Skip to content

⚡️ Speed up method ProcessManager.handle_sigterm by 2,736% in PR #9674 (auto-login-removals)#9815

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

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

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,736% (27.36x) speedup for ProcessManager.handle_sigterm in langflow/__main__.py

⏱️ Runtime : 2.25 milliseconds 79.4 microseconds (best of 101 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 was optimized:

  • Added a shutdown_in_progress check at the start of shutdown() that immediately returns if shutdown is already happening
  • Moved the self.shutdown_in_progress = True assignment to the beginning of shutdown() to ensure the flag is set before any cleanup work begins

Why this creates a massive speedup:
The line profiler reveals the critical issue: in the original code, shutdown() was being called multiple times (310 hits), with each call executing the full shutdown logic including process cleanup and farewell message printing. The optimized version shows that after the first call sets shutdown_in_progress = True, all subsequent calls (309 out of 310) immediately return without doing any work.

The performance gain comes from:

  • Eliminating redundant process operations: Multiple calls to webapp_process.terminate(), webapp_process.join(), and print_farewell_message()
  • Avoiding repeated I/O operations: The farewell message printing (print_farewell_message()) was consuming 89.3% of the shutdown time in the original version
  • Preventing race conditions: In concurrent signal scenarios, multiple threads could trigger shutdown simultaneously

Test case performance:
This optimization particularly benefits test cases that involve:

  • Multiple rapid SIGTERM signals (test_sigterm_many_processes_sequential - 100 iterations)
  • Concurrent shutdown scenarios (test_sigterm_already_shutting_down)
  • Large-scale process management (test_sigterm_large_process_list)

The 2736% speedup demonstrates how critical it is to prevent duplicate expensive operations in signal handlers and shutdown paths.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 424 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import builtins
# function to test
import platform
import sys
import threading
import types
import types as _types

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


# Dummy logger for testing
class DummyLogger:
    def __init__(self):
        self.warnings = []

    def warning(self, msg):
        self.warnings.append(msg)

dummy_logger = DummyLogger()

# Dummy Process class for testing
class DummyProcess:
    def __init__(self, alive=True, terminate_ok=True, kill_ok=True):
        self._alive = alive
        self.terminated = False
        self.killed = False
        self.terminate_called = 0
        self.kill_called = 0
        self.join_called = []
        self.terminate_ok = terminate_ok
        self.kill_ok = kill_ok

    def is_alive(self):
        return self._alive

    def terminate(self):
        self.terminate_called += 1
        self.terminated = True
        if self.terminate_ok:
            self._alive = False

    def join(self, timeout=None):
        self.join_called.append(timeout)
        # Simulate process not dying if terminate_ok is False
        if not self.terminate_ok and not self.kill_ok:
            self._alive = True
        elif not self.terminate_ok and self.kill_ok and self.killed:
            self._alive = False

    def kill(self):
        self.kill_called += 1
        self.killed = True
        if self.kill_ok:
            self._alive = False

# Dummy ProgressIndicator for testing
class DummyProgressIndicator:
    def __init__(self):
        self.shutdown_requested = False
        self.cleaned_up = False

    def request_shutdown(self):
        self.shutdown_requested = True

    def cleanup(self):
        self.cleaned_up = True

# Patch click.echo and click.style for capturing output
class EchoCapture:
    def __init__(self):
        self.lines = []

    def __call__(self, *args, **kwargs):
        if args:
            self.lines.append(args[0])

class StyleCapture:
    def __init__(self):
        self.last_args = None

    def __call__(self, text, **kwargs):
        self.last_args = (text, kwargs)
        return f"STYLED({text})"

echo_capture = EchoCapture()
style_capture = StyleCapture()

########################
# 1. Basic Test Cases  #
########################

def test_sigterm_no_progress_no_process():
    """Test: SIGTERM with no progress indicator and no webapp process."""
    mgr = ProcessManager()
    mgr.progress_indicator = None
    mgr.webapp_process = None
    mgr.handle_sigterm(15, None)

def test_sigterm_with_progress_indicator():
    """Test: SIGTERM with a progress indicator present."""
    mgr = ProcessManager()
    dummy_pi = DummyProgressIndicator()
    mgr.progress_indicator = dummy_pi
    mgr.webapp_process = None
    mgr.handle_sigterm(15, None)

def test_sigterm_with_alive_process():
    """Test: SIGTERM with a running webapp process."""
    mgr = ProcessManager()
    mgr.progress_indicator = None
    proc = DummyProcess(alive=True)
    mgr.webapp_process = proc
    mgr.handle_sigterm(15, None)

def test_sigterm_with_progress_and_alive_process():
    """Test: SIGTERM with both progress indicator and alive process."""
    mgr = ProcessManager()
    dummy_pi = DummyProgressIndicator()
    mgr.progress_indicator = dummy_pi
    proc = DummyProcess(alive=True)
    mgr.webapp_process = proc
    mgr.handle_sigterm(15, None)

def test_sigterm_already_shutting_down():
    """Test: SIGTERM called while shutdown is already in progress."""
    mgr = ProcessManager()
    mgr.shutdown_in_progress = True
    dummy_pi = DummyProgressIndicator()
    mgr.progress_indicator = dummy_pi
    proc = DummyProcess(alive=True)
    mgr.webapp_process = proc
    mgr.handle_sigterm(15, None)

########################
# 2. Edge Test Cases   #
########################

def test_sigterm_process_refuses_to_die():
    """Test: SIGTERM with process that refuses to die, triggers kill and warning."""
    mgr = ProcessManager()
    proc = DummyProcess(alive=True, terminate_ok=False, kill_ok=True)
    mgr.webapp_process = proc
    mgr.progress_indicator = None
    mgr.handle_sigterm(15, None)

def test_sigterm_process_refuses_to_die_even_after_kill():
    """Test: SIGTERM with process that refuses to die even after kill, triggers two warnings."""
    mgr = ProcessManager()
    proc = DummyProcess(alive=True, terminate_ok=False, kill_ok=False)
    mgr.webapp_process = proc
    mgr.progress_indicator = None
    mgr.handle_sigterm(15, None)

def test_sigterm_process_is_none():
    """Test: SIGTERM when webapp_process is None."""
    mgr = ProcessManager()
    mgr.webapp_process = None
    mgr.progress_indicator = None
    mgr.handle_sigterm(15, None)

def test_sigterm_process_not_alive():
    """Test: SIGTERM when webapp_process is present but not alive."""
    mgr = ProcessManager()
    proc = DummyProcess(alive=False)
    mgr.webapp_process = proc
    mgr.progress_indicator = None
    mgr.handle_sigterm(15, None)

def test_sigterm_progress_indicator_cleanup_exception(monkeypatch):
    """Test: SIGTERM when progress indicator cleanup raises exception (should not stop shutdown)."""
    mgr = ProcessManager()
    class BadProgress(DummyProgressIndicator):
        def cleanup(self):
            raise RuntimeError("Cleanup failed!")
    bad_pi = BadProgress()
    mgr.progress_indicator = bad_pi
    proc = DummyProcess(alive=True)
    mgr.webapp_process = proc
    # Patch shutdown to catch the exception and continue
    orig_cleanup = bad_pi.cleanup
    def safe_cleanup():
        try:
            orig_cleanup()
        except Exception:
            pass
    bad_pi.cleanup = safe_cleanup
    mgr.handle_sigterm(15, None)

#############################
# 3. Large Scale Test Cases #
#############################

def test_sigterm_many_progress_indicators():
    """Test: SIGTERM with many progress indicators (simulate by calling handle_sigterm repeatedly)."""
    mgr = ProcessManager()
    # Simulate a scenario where multiple progress indicators are set/cleaned up
    for _ in range(100):
        mgr.progress_indicator = DummyProgressIndicator()
        mgr.webapp_process = None
        mgr.shutdown_in_progress = False
        mgr.handle_sigterm(15, None)

def test_sigterm_many_processes_sequential():
    """Test: SIGTERM on many processes sequentially (simulate by calling handle_sigterm repeatedly)."""
    mgr = ProcessManager()
    for _ in range(100):
        proc = DummyProcess(alive=True)
        mgr.webapp_process = proc
        mgr.progress_indicator = None
        mgr.shutdown_in_progress = False
        mgr.handle_sigterm(15, None)


def test_sigterm_large_process_list():
    """Test: SIGTERM on a manager with a large list of processes (simulate by iterating)."""
    for _ in range(100):
        mgr = ProcessManager()
        proc = DummyProcess(alive=True)
        mgr.webapp_process = proc
        mgr.progress_indicator = None
        mgr.handle_sigterm(15, None)
# 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 click
# imports
import pytest  # used for our unit tests
from langflow.__main__ import ProcessManager


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

# Patch logger in the module
logger = DummyLogger()

class DummyProcess:
    """A dummy process for testing process handling."""
    def __init__(self, alive=True, join_side_effect=None, terminate_side_effect=None, kill_side_effect=None):
        self._alive = alive
        self.terminated = False
        self.killed = False
        self.join_calls = []
        self.terminate_calls = 0
        self.kill_calls = 0
        self._join_side_effect = join_side_effect or []
        self._terminate_side_effect = terminate_side_effect
        self._kill_side_effect = kill_side_effect

    def is_alive(self):
        return self._alive

    def terminate(self):
        self.terminated = True
        self.terminate_calls += 1
        if self._terminate_side_effect:
            self._terminate_side_effect()
        # Simulate process no longer alive after terminate unless join_side_effect keeps it alive

    def join(self, timeout=None):
        self.join_calls.append(timeout)
        # If join_side_effect is provided, use it to determine if still alive
        if self._join_side_effect:
            # Pop the next alive state
            self._alive = self._join_side_effect.pop(0)
        else:
            # By default, after join, not alive
            self._alive = False

    def kill(self):
        self.killed = True
        self.kill_calls += 1
        if self._kill_side_effect:
            self._kill_side_effect()
        # Simulate process no longer alive after kill unless join_side_effect keeps it alive

# unit tests

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


def test_sigterm_no_progress_indicator():
    """Basic: SIGTERM with no progress indicator does not raise."""
    pm = ProcessManager()
    pm.progress_indicator = None
    pm.webapp_process = None
    pm.handle_sigterm(15, None)

To edit these changes git checkout codeflash/optimize-pr9674-2025-09-11T02.39.38 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 was optimized:**
- Added a `shutdown_in_progress` check at the start of `shutdown()` that immediately returns if shutdown is already happening
- Moved the `self.shutdown_in_progress = True` assignment to the beginning of `shutdown()` to ensure the flag is set before any cleanup work begins

**Why this creates a massive speedup:**
The line profiler reveals the critical issue: in the original code, `shutdown()` was being called multiple times (310 hits), with each call executing the full shutdown logic including process cleanup and farewell message printing. The optimized version shows that after the first call sets `shutdown_in_progress = True`, all subsequent calls (309 out of 310) immediately return without doing any work.

The performance gain comes from:
- **Eliminating redundant process operations**: Multiple calls to `webapp_process.terminate()`, `webapp_process.join()`, and `print_farewell_message()` 
- **Avoiding repeated I/O operations**: The farewell message printing (`print_farewell_message()`) was consuming 89.3% of the shutdown time in the original version
- **Preventing race conditions**: In concurrent signal scenarios, multiple threads could trigger shutdown simultaneously

**Test case performance:**
This optimization particularly benefits test cases that involve:
- Multiple rapid SIGTERM signals (`test_sigterm_many_processes_sequential` - 100 iterations)
- Concurrent shutdown scenarios (`test_sigterm_already_shutting_down`)
- Large-scale process management (`test_sigterm_large_process_list`)

The 2736% speedup demonstrates how critical it is to prevent duplicate expensive operations in signal handlers and shutdown paths.
@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

@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.39.38 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.

0 participants