Skip to content

Refactor AppHarness to use reflex run subprocess instead of threads#5555

Merged
adhami3310 merged 25 commits into
mainfrom
devin/ENG-6317-1752182691
Jul 16, 2025
Merged

Refactor AppHarness to use reflex run subprocess instead of threads#5555
adhami3310 merged 25 commits into
mainfrom
devin/ENG-6317-1752182691

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot commented Jul 10, 2025

Refactor AppHarness to use subprocess instead of threads

Summary

This PR refactors the AppHarness class in reflex/testing.py to use reflex run in a subprocess instead of thread-based server management. This aligns better with how Reflex apps normally run (using granian by default) and reduces custom code maintenance burden.

Key Changes:

  • Replaced thread-based _start_backend and _start_frontend methods with subprocess-based _start_subprocess
  • Added _wait_for_servers method that parses console output to detect server readiness using regex patterns
  • Implemented shell=True approach for subprocess to properly capture stdout/stderr from reflex run
  • Added proper process cleanup in stop() method to terminate subprocesses
  • Fixed all darglint documentation errors for parameter and return type documentation
  • Maintained API compatibility where possible (state management methods now raise NotImplementedError)

Current Status:

  • All 12 integration-app-harness CI tests are still failing, but this is expected as the refactoring is not yet complete
  • Local testing shows the subprocess implementation correctly starts servers and detects readiness
  • All pre-commit checks (darglint, ruff, pyright) are now passing

Review & Testing Checklist for Human

🔴 High Risk - 5 items to verify:

  • Test subprocess implementation locally: Run integration tests locally to verify servers start correctly and process cleanup works (no hanging reflex run processes after tests)
  • Verify console output parsing reliability: Test that the regex patterns in _wait_for_servers correctly detect "App running at:" and "Backend running at:" messages across different environments
  • Check process cleanup edge cases: Test that subprocess termination works correctly when tests fail or are interrupted (e.g., with Ctrl+C)
  • Test with different state managers: Verify the refactored AppHarness works with both memory and Redis state managers
  • Validate API compatibility: Check that existing tests that use app_harness.get_state() still work correctly with the new subprocess approach

Recommended Test Plan:

  1. Run uv run pytest tests/integration/test_input.py::test_fully_controlled_input -v -s locally
  2. Check ps aux | grep reflex before and after tests to verify no hanging processes
  3. Test with REDIS_URL=redis://localhost:6379 to verify Redis state manager compatibility
  4. Review CI logs for specific integration test failure patterns to understand remaining issues

Diagram

%%{ init : { "theme" : "default" }}%%
graph TD
    Testing["reflex/testing.py<br/>AppHarness class"]:::major-edit
    Processes["reflex/utils/processes.py<br/>new_process helper"]:::context
    IntegrationTests["tests/integration/<br/>12 failing tests"]:::context
    
    Testing -->|"uses subprocess"| Processes
    Testing -->|"starts via"| ReflexRun["reflex run command<br/>with shell=True"]:::minor-edit
    IntegrationTests -->|"instantiate"| Testing
    
    subgraph "AppHarness Methods"
        StartSubprocess["_start_subprocess()<br/>NEW METHOD"]:::major-edit
        WaitServers["_wait_for_servers()<br/>NEW METHOD"]:::major-edit
        Stop["stop()<br/>UPDATED"]:::minor-edit
    end
    
    Testing --> StartSubprocess
    Testing --> WaitServers
    Testing --> Stop
    
    subgraph Legend
        L1[Major Edit]:::major-edit
        L2[Minor Edit]:::minor-edit
        L3[Context/No Edit]:::context
    end

classDef major-edit fill:#90EE90
classDef minor-edit fill:#87CEEB
classDef context fill:#FFFFFF
Loading

Notes

  • Link to Devin run: https://app.devin.ai/sessions/bc06be7219144a1fb3ff1795617e9a29
  • Requested by: masen@reflex.dev
  • Linear ticket: ENG-6317
  • The shell=True approach was critical for properly capturing subprocess output - previous attempts without it failed to detect server readiness
  • Integration test failures are expected at this stage; the goal was to get the basic subprocess infrastructure working
  • All darglint errors have been resolved by adding proper parameter documentation and fixing return type documentation

- Replace _start_backend and _start_frontend thread methods with _start_subprocess
- Use new_process from reflex/utils/processes.py for subprocess management
- Implement console output parsing to detect server startup
- Update port management using handle_port for dynamic port assignment
- Maintain API compatibility for state management methods
- Update process cleanup for proper subprocess termination
- Add compatibility attributes and methods for existing tests
- Fix type annotations to resolve pyright variable override errors
- Add port parameter to _start_backend for test compatibility
- Fix docstring documentation for darglint compliance

Fixes ENG-6317

Co-Authored-By: masen@reflex.dev <masen@reflex.dev>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@linear
Copy link
Copy Markdown

linear Bot commented Jul 10, 2025

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Major architectural change to reflex/testing.py replacing the thread-based AppHarness implementation with a subprocess-based approach using 'reflex run' command.

  • Eliminates direct uvicorn server and frontend thread management in favor of unified subprocess control
  • Introduces port management for both frontend and backend servers through reflex subprocess
  • Removes custom TCP server implementation for static file serving
  • Adds process lifecycle management with proper cleanup handling
  • Maintains backward compatibility through deprecated thread-related fields

1 file reviewed, 2 comments
Edit PR Review Bot Settings | Greptile

Comment thread reflex/testing.py Outdated
Comment thread reflex/testing.py Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jul 10, 2025

CodSpeed Performance Report

Merging #5555 will not alter performance

Comparing devin/ENG-6317-1752182691 (a709884) with main (60b1baf)

Summary

✅ 8 untouched benchmarks

devin-ai-integration Bot and others added 24 commits July 10, 2025 22:04
- Add timeout and non-blocking stdout reading in _wait_for_servers
- Simplify process cleanup logic in stop() method
- Add Windows compatibility for subprocess stdout handling
- Use daemon threads to avoid hanging on cleanup
- Add proper error handling for process termination

Fixes CI timeout issues in integration tests

Co-Authored-By: masen@reflex.dev <masen@reflex.dev>
- Change from subprocess.Popen with argument list to shell=True with command string
- Debug testing confirmed shell=True successfully captures both frontend and backend messages
- Add fallback backend detection using port checking for additional robustness
- Resolves core subprocess stdout capture issue causing all 12 integration test failures
- Remove all debugging print statements and fix ruff-check issues

Based on comprehensive subprocess behavior testing that showed:
- Basic subprocess approach: Frontend=False, Backend=False
- Shell=True approach: Frontend=True, Backend=True

Co-Authored-By: masen@reflex.dev <masen@reflex.dev>
* enable logging to stdout
* allow prod mode to work
* better port selection algorithm
* remove useless code
* cleaner teardown
* test fixups for compatibility
- Add proper docstring to nested consume_output function in _wait_for_servers method
- Resolves darglint compliance issue for subprocess implementation
- All other pre-commit hooks (ruff-format, ruff-check, codespell, prettier) pass

Co-Authored-By: masen@reflex.dev <masen@reflex.dev>
- Resolved merge conflict in reflex/testing.py
- Added proper parameter documentation to _start_subprocess and _wait_for_servers methods
- Fixed docstring documentation for set_state and modify_state to match actual NotImplementedError behavior
- Updated get_state docstring to only document AssertionError (matches actual code)
- All darglint checks now pass while preserving subprocess implementation improvements
- Applied ruff formatting fixes

Co-Authored-By: masen@reflex.dev <masen@reflex.dev>
Allow writing the console.error messages to a separate log file. Mainly used for testing or monitoring for errors.
Remove complex loops and threads from subprocess management in favor of log files.

Use a separate error log to check if any errors should be raised in the test process.

Raise error if reflex exits on its own with a non-zero code
it throws an error with the source map builder, and OOM without node 22
rx.config is no longer the local app config, since it runs in a subprocess
Avoid issue where we try to restart reflex, but the port is still taken by the
process we just killed. To properly test the reconnect logic, the backend needs
to come back up on the same port as before.
Comment on lines +183 to +190
# Bring the backend back up once the port is free'd.
if result := AppHarness._poll_for(
lambda: processes.handle_port(
"backend", connection_banner.backend_port or 0, auto_increment=False
),
timeout=120,
):
print(f"Port {result} is now free.")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately this seems to add lots of time in the CI, not sure why the port takes so long to be available again after stopping reflex... @adhami3310 ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fun, i think we can improve this a tad bit by printing why a port is closed, let me try that in main

@adhami3310 adhami3310 merged commit 9c6cb12 into main Jul 16, 2025
41 checks passed
@adhami3310 adhami3310 deleted the devin/ENG-6317-1752182691 branch July 16, 2025 21:08
adhami3310 added a commit that referenced this pull request Jul 21, 2025
adhami3310 added a commit that referenced this pull request Jul 21, 2025
* Revert "remove timeout and simplify wait for server (#5594)"

This reverts commit 9114d21.

* Revert "Refactor AppHarness to use reflex run subprocess instead of threads (#5555)"

This reverts commit 9c6cb12.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants