Skip to content

⚡️ Speed up method ProcessManager.handle_sigint by 2,283% in PR #9674 (auto-login-removals)#9816

Closed
codeflash-ai[bot] wants to merge 2 commits into
auto-login-removalsfrom
codeflash/optimize-pr9674-2025-09-11T02.57.32
Closed

⚡️ Speed up method ProcessManager.handle_sigint by 2,283% in PR #9674 (auto-login-removals)#9816
codeflash-ai[bot] wants to merge 2 commits into
auto-login-removalsfrom
codeflash/optimize-pr9674-2025-09-11T02.57.32

Conversation

@codeflash-ai
Copy link
Copy Markdown
Contributor

@codeflash-ai codeflash-ai Bot commented Sep 11, 2025

⚡️ This pull request contains optimizations for PR #9674

If you approve this dependent PR, these changes will be merged into the original PR branch auto-login-removals.

This PR will be automatically closed if the original PR is merged.


📄 2,283% (22.83x) speedup for ProcessManager.handle_sigint in langflow/__main__.py

⏱️ Runtime : 1.32 milliseconds 55.2 microseconds (best of 83 runs)

📝 Explanation and details

The key optimization is adding an early return guard in the shutdown() method to prevent redundant execution when shutdown is already in progress.

What changed:

  • Added if self.shutdown_in_progress: return at the start of shutdown()
  • Set self.shutdown_in_progress = True immediately after the guard

Why this creates a massive speedup:
The profiler reveals that shutdown() was being called repeatedly (111 times) and consuming 88.9% of execution time in print_farewell_message() alone. The optimized version short-circuits after the first call - subsequent calls hit the early return and execute in microseconds instead of milliseconds.

Performance impact by test type:

  • Rapid succession tests (like test_sigint_large_many_sigints with 100 calls): Massive benefit as only the first call does real work
  • Single call tests: Minimal overhead from the additional guard check
  • Multiple ProcessManager tests: Each instance still performs full shutdown once, so optimization is per-instance

The 2283% speedup occurs because the expensive operations (process termination, thread cleanup, I/O operations) now only happen once instead of being repeated for every redundant shutdown call.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 324 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import platform
import sys
import threading
from types import FrameType
from typing import Any

# imports
import pytest
from langflow.__main__ import ProcessManager
from multiprocess.context import Process

# function to test (already provided above)

# --- Unit Tests for handle_sigint ---

class DummyProcess:
    """A dummy process class to simulate Process behavior for testing."""
    def __init__(self, alive=True):
        self._alive = alive
        self.terminated = False
        self.killed = False
        self.join_called = []
    def is_alive(self):
        return self._alive
    def terminate(self):
        self.terminated = True
        self._alive = False
    def kill(self):
        self.killed = True
        self._alive = False
    def join(self, timeout=None):
        self.join_called.append(timeout)
        # Simulate process dying after join
        self._alive = False

class DummyProgressIndicator:
    """A dummy progress indicator to track shutdown and cleanup calls."""
    def __init__(self):
        self.shutdown_requested = False
        self.cleaned_up = False
        self.request_shutdown_called = 0
        self.cleanup_called = 0
    def request_shutdown(self):
        self.shutdown_requested = True
        self.request_shutdown_called += 1
    def cleanup(self):
        self.cleaned_up = True
        self.cleanup_called += 1
        self.request_shutdown()

class DummyLogger:
    """Dummy logger to capture warning messages."""
    def __init__(self):
        self.warnings = []
    def warning(self, msg):
        self.warnings.append(msg)

@pytest.fixture(autouse=True)
def patch_logger(monkeypatch):
    # Patch the logger in the module
    dummy_logger = DummyLogger()
    monkeypatch.setattr("langflow.logging.logger", dummy_logger)
    yield dummy_logger

@pytest.fixture
def process_manager():
    # Create a fresh ProcessManager for each test
    return ProcessManager()

# ----------- BASIC TEST CASES -----------

def test_sigint_basic_shutdown_initiates_shutdown(process_manager):
    """Test that handle_sigint initiates shutdown and sets shutdown_in_progress."""
    process_manager.shutdown_in_progress = False
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.handle_sigint(2, None)

def test_sigint_basic_no_progress_indicator(process_manager):
    """Test handle_sigint when no progress indicator is present."""
    process_manager.progress_indicator = None
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_basic_no_webapp_process(process_manager):
    """Test handle_sigint when no webapp_process is present."""
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = None
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_basic_already_shutting_down(process_manager):
    """Test that handle_sigint does nothing if shutdown is already in progress."""
    process_manager.shutdown_in_progress = True
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.handle_sigint(2, None)

# ----------- EDGE TEST CASES -----------

def test_sigint_edge_process_not_alive(process_manager):
    """Test handle_sigint when process is not alive (should not terminate/kill)."""
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=False)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_edge_process_stays_alive_after_terminate_and_kill(monkeypatch, process_manager, patch_logger):
    """Test process that stays alive after terminate and kill triggers logger warnings."""
    class StubbornProcess(DummyProcess):
        def __init__(self):
            super().__init__(alive=True)
            self.join_count = 0
        def terminate(self):
            self.terminated = True
            # Still alive after terminate
        def kill(self):
            self.killed = True
            # Still alive after kill
        def join(self, timeout=None):
            self.join_count += 1
            # Always alive
            self._alive = True
    stubborn = StubbornProcess()
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = stubborn
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_edge_multiple_calls(process_manager):
    """Test multiple calls to handle_sigint only perform shutdown once."""
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)
    # Second call should not repeat shutdown
    process_manager.handle_sigint(2, None)

def test_sigint_edge_progress_indicator_cleanup_called(process_manager):
    """Test that cleanup is called on progress indicator during shutdown."""
    progress = DummyProgressIndicator()
    process_manager.progress_indicator = progress
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_edge_farewell_message(monkeypatch, process_manager):
    """Test that print_farewell_message outputs the correct message."""
    output = []
    monkeypatch.setattr(sys, "stdout", type("DummyStdout", (), {"write": lambda self, s: output.append(s), "flush": lambda self: None})())
    monkeypatch.setattr("click.echo", lambda *args, **kwargs: output.append("echo"))
    monkeypatch.setattr("click.style", lambda text, **kwargs: f"styled:{text}")
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

# ----------- LARGE SCALE TEST CASES -----------

def test_sigint_large_many_sigints(process_manager):
    """Test handle_sigint called many times in rapid succession."""
    process_manager.progress_indicator = DummyProgressIndicator()
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    for _ in range(100):
        process_manager.handle_sigint(2, None)

def test_sigint_large_many_processes():
    """Test many ProcessManager instances handling sigint independently."""
    managers = []
    for i in range(100):
        pm = ProcessManager()
        pm.progress_indicator = DummyProgressIndicator()
        pm.webapp_process = DummyProcess(alive=True)
        pm.shutdown_in_progress = False
        pm.handle_sigint(2, None)
        managers.append(pm)
    # Each manager should have shutdown in progress and process terminated
    for pm in managers:
        pass

def test_sigint_large_progress_indicator_steps_cleanup(process_manager):
    """Test progress indicator with many steps is cleaned up correctly."""
    class ManyStepsProgress(DummyProgressIndicator):
        def __init__(self):
            super().__init__()
            self.steps = [{"step": i} for i in range(999)]
    progress = ManyStepsProgress()
    process_manager.progress_indicator = progress
    process_manager.webapp_process = DummyProcess(alive=True)
    process_manager.shutdown_in_progress = False
    process_manager.handle_sigint(2, None)

def test_sigint_large_dummy_processes_termination():
    """Test termination of many dummy processes."""
    processes = [DummyProcess(alive=True) for _ in range(999)]
    for proc in processes:
        proc.terminate()
        proc.join(timeout=30)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import platform
import sys
import threading
import time

# function to test
import click
# imports
import pytest  # used for our unit tests
from langflow.__main__ import ProcessManager


# Dummy Process for testing
class DummyProcess:
    def __init__(self, alive=True):
        self._alive = alive
        self.terminated = False
        self.killed = False
        self.joined = 0

    def is_alive(self):
        return self._alive

    def terminate(self):
        self.terminated = True
        self._alive = False

    def kill(self):
        self.killed = True
        self._alive = False

    def join(self, timeout=None):
        self.joined += 1
        # Simulate process dying after join
        self._alive = False

# unit tests

# ----------- Basic Test Cases -----------




def test_sigint_without_progress_indicator():
    """SIGINT should not fail if no progress indicator is set."""
    pm = ProcessManager()
    pm.progress_indicator = None
    pm.handle_sigint(2, None)

To edit these changes git checkout codeflash/optimize-pr9674-2025-09-11T02.57.32 and push.

Codeflash

The key optimization is adding an **early return guard** in the `shutdown()` method to prevent redundant execution when shutdown is already in progress.

**What changed:**
- Added `if self.shutdown_in_progress: return` at the start of `shutdown()` 
- Set `self.shutdown_in_progress = True` immediately after the guard

**Why this creates a massive speedup:**
The profiler reveals that `shutdown()` was being called repeatedly (111 times) and consuming 88.9% of execution time in `print_farewell_message()` alone. The optimized version short-circuits after the first call - subsequent calls hit the early return and execute in microseconds instead of milliseconds.

**Performance impact by test type:**
- **Rapid succession tests** (like `test_sigint_large_many_sigints` with 100 calls): Massive benefit as only the first call does real work
- **Single call tests**: Minimal overhead from the additional guard check
- **Multiple ProcessManager tests**: Each instance still performs full shutdown once, so optimization is per-instance

The 2283% speedup occurs because the expensive operations (process termination, thread cleanup, I/O operations) now only happen once instead of being repeated for every redundant shutdown call.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 11, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

@mohammedahmed18
Copy link
Copy Markdown

this is a mistake, it was already fixed, we will release a new codeflash version very soon

@codeflash-ai codeflash-ai Bot closed this Sep 11, 2025
@codeflash-ai
Copy link
Copy Markdown
Contributor Author

codeflash-ai Bot commented Sep 11, 2025

This PR has been automatically closed because the original PR #9674 by jordanrfrazier was closed.

@codeflash-ai codeflash-ai Bot deleted the codeflash/optimize-pr9674-2025-09-11T02.57.32 branch September 11, 2025 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant