Summary
Remove the ExecutorSafeFunctions class, ExecutorType environment configuration, and all concurrent.futures-based parallelism from the compilation pipeline. Replace with straightforward sequential execution.
This is a cleanup task that can be done as part of ENG-9144 or independently. It's broken out separately because the executor removal is self-contained and can be reviewed independently of the larger plugin integration.
Background
The current compiler has vestiges of a complex parallelization strategy:
ExecutorSafeFunctions (in reflex/compiler/compiler.py)
A helper class that stashes component data in class-level attributes so it can be accessed by forked child processes (since the data isn't picklable). Methods:
compile_page(route) — compiles a page from the stashed data
compile_unevaluated_page(route, style, theme) — evaluates and compiles a page
compile_theme(style) — compiles the theme
ExecutorType (in reflex/environment.py)
An enum that selects between ThreadPoolExecutor and ProcessPoolExecutor based on an environment variable (REFLEX_COMPILE_EXECUTOR).
Usage in App._compile() (in reflex/app.py)
executor = ExecutorType.get_executor_from_environment()
for route, component in zip(self._pages, page_components, strict=True):
ExecutorSafeFunctions.COMPONENTS[route] = component
with executor as executor:
result_futures = []
for route in self._pages:
f = executor.submit(ExecutorSafeFunctions.compile_page, route)
result_futures.append(f)
# ... submit stylesheet and theme compilation too
for future in concurrent.futures.as_completed(result_futures):
...
Why this should be removed
As stated in the project brief: "The compiler is largely CPU bound and doesn't benefit from python threads due to the GIL and multiprocessing incurs significant enough overhead that it doesn't improve compile times for most apps."
The parallelism adds significant complexity:
ExecutorSafeFunctions with its class-level data stashing is a workaround for unpicklable data
- Process pool executor requires forking, which has its own issues (copy-on-write memory, module reimports)
- Thread pool executor is limited by the GIL for CPU-bound work
- The progress bar tracking has to work across process boundaries
- Error handling across futures is more complex
What to Do
Remove
Replace with
Simple sequential calls:
compile_results = []
for route in self._pages:
compile_results.append(compiler.compile_page(route, page_components[route]))
progress.advance(task)
compile_results.append(compiler.compile_root_stylesheet(self.stylesheets, self.reset_style))
compile_results.append(compiler.compile_theme(self.style))
Keep
- The
progress bar advancement (just call it directly instead of via callbacks)
- Plugin
pre_compile hooks (these use a different mechanism)
compile_theme, compile_page, compile_root_stylesheet functions themselves — just call them directly
Acceptance Criteria
Key Files
reflex/compiler/compiler.py — ExecutorSafeFunctions class
reflex/app.py — App._compile(), executor usage
reflex/environment.py — ExecutorType enum
Summary
Remove the
ExecutorSafeFunctionsclass,ExecutorTypeenvironment configuration, and allconcurrent.futures-based parallelism from the compilation pipeline. Replace with straightforward sequential execution.This is a cleanup task that can be done as part of ENG-9144 or independently. It's broken out separately because the executor removal is self-contained and can be reviewed independently of the larger plugin integration.
Background
The current compiler has vestiges of a complex parallelization strategy:
ExecutorSafeFunctions(inreflex/compiler/compiler.py)A helper class that stashes component data in class-level attributes so it can be accessed by forked child processes (since the data isn't picklable). Methods:
compile_page(route)— compiles a page from the stashed datacompile_unevaluated_page(route, style, theme)— evaluates and compiles a pagecompile_theme(style)— compiles the themeExecutorType(inreflex/environment.py)An enum that selects between
ThreadPoolExecutorandProcessPoolExecutorbased on an environment variable (REFLEX_COMPILE_EXECUTOR).Usage in
App._compile()(inreflex/app.py)Why this should be removed
As stated in the project brief: "The compiler is largely CPU bound and doesn't benefit from python threads due to the GIL and multiprocessing incurs significant enough overhead that it doesn't improve compile times for most apps."
The parallelism adds significant complexity:
ExecutorSafeFunctionswith its class-level data stashing is a workaround for unpicklable dataWhat to Do
Remove
ExecutorSafeFunctionsclass inreflex/compiler/compiler.pyExecutorTypeenum inreflex/environment.py(or just the compile executor variant)REFLEX_COMPILE_EXECUTORenvironment variable handlingconcurrent.futuresimports and usage inApp._compile()result_futures/as_completedpatternReplace with
Simple sequential calls:
Keep
progressbar advancement (just call it directly instead of via callbacks)pre_compilehooks (these use a different mechanism)compile_theme,compile_page,compile_root_stylesheetfunctions themselves — just call them directlyAcceptance Criteria
ExecutorSafeFunctionsclass is deletedconcurrent.futuresusage remains in the compilation pipelineREFLEX_COMPILE_EXECUTORenv var is no longer referencedKey Files
reflex/compiler/compiler.py—ExecutorSafeFunctionsclassreflex/app.py—App._compile(), executor usagereflex/environment.py—ExecutorTypeenum