Skip to content

Add context manager support to InferencePipeline - #2939

Open
KushagraKanaujia wants to merge 1 commit into
roboflow:mainfrom
KushagraKanaujia:feature/inference-pipeline-context-manager-2744
Open

Add context manager support to InferencePipeline#2939
KushagraKanaujia wants to merge 1 commit into
roboflow:mainfrom
KushagraKanaujia:feature/inference-pipeline-context-manager-2744

Conversation

@KushagraKanaujia

Copy link
Copy Markdown

Summary

Adds Python context manager protocol (__enter__ / __exit__) to InferencePipeline, enabling safe resource cleanup with the with statement.

Problem

Currently, InferencePipeline requires manual lifecycle management that is prone to resource leaks:

pipeline = InferencePipeline.init(...)
pipeline.start()
pipeline.join()  # ❌ Never reached if exception occurs!

If any exception occurs between start() and join(), join() is never reached, leaking:

  • Orphaned inference thread (continues running in background)
  • Orphaned dispatching thread (continues running in background)
  • ThreadPoolExecutor never shuts down (leaked worker threads)
  • Profiling traces never saved to disk (data loss)

Users must write verbose try/finally blocks:

pipeline = InferencePipeline.init(...)
try:
    pipeline.start()
except Exception:
    pipeline.terminate()
    pipeline.join()
    raise
pipeline.join()

This is error-prone and boilerplate-heavy.

Solution

Implemented context manager protocol following the existing pattern used by VideoFileSink (already in codebase at sinks.py:547-551).

Implementation

Added two methods to InferencePipeline:

def __enter__(self) -> "InferencePipeline":
    return self

def __exit__(self, exc_type, exc_val, exc_tb) -> None:
    self.terminate()
    self.join()

Total: ~40 lines (including comprehensive docstrings with examples)

Usage

Before (manual - error-prone)

pipeline = InferencePipeline.init(
    video_reference="./video.mp4",
    model_id="my-model/1",
    on_prediction=my_sink,
)
try:
    pipeline.start()
except Exception:
    pipeline.terminate()
    pipeline.join()
    raise
pipeline.join()

After (automatic - safe)

with InferencePipeline.init(
    video_reference="./video.mp4",
    model_id="my-model/1",
    on_prediction=my_sink,
) as pipeline:
    pipeline.start()
# terminate() + join() called automatically, even on exceptions ✅

Benefits

  • Prevents resource leaks: Threads, thread pools, profiling data always cleaned up
  • Pythonic API: Uses standard with statement pattern
  • Consistency: Follows existing VideoFileSink pattern in same codebase
  • Backward compatible: Existing manual lifecycle code continues to work
  • Well documented: Comprehensive docstrings with usage examples
  • Fully tested: 8 test cases covering all scenarios

Testing

Added comprehensive test suite in test_context_manager.py:

Test Coverage:

  • __enter__ returns pipeline instance
  • __exit__ calls terminate() then join() in correct order
  • ✅ Cleanup occurs even when exceptions are raised
  • ✅ Exceptions propagate after cleanup (not suppressed)
  • ✅ Typical usage patterns from issue work correctly
  • ✅ Edge cases (terminate failure, etc.)

All tests use mocking to verify behavior without requiring actual video sources or models.

Design Decisions

Why this approach?

  1. Consistency: VideoFileSink already uses this exact pattern (sinks.py:547-551)
  2. Simplicity: Minimal code (~40 lines with docs)
  3. Safety: Guarantees cleanup even on exceptions
  4. Standard: Python context manager protocol is well-understood

Why terminate() then join()?

From the issue (#2744):

terminate() signals threads to stop (self._stop = True) and purges buffers
join() waits for threads to complete AND calls on_pipeline_end (thread pool shutdown, profiling save)

Both must be called for clean shutdown. Order matters: signal stop first, then wait for completion.

Files Changed

inference/core/interfaces/stream/inference_pipeline.py

  • Added __enter__() method (returns self)
  • Added __exit__() method (calls terminate() + join())
  • Added docstrings with usage examples

tests/.../stream/test_context_manager.py

  • New test module with 8 comprehensive test cases
  • Tests normal flow, exception handling, cleanup guarantees
  • Uses mocking for fast, deterministic tests

Backward Compatibility

100% backward compatible - No breaking changes

Existing code using manual lifecycle continues to work:

pipeline.start()
pipeline.join()  # Still works!

New code can use context manager for safety:

with pipeline:
    pipeline.start()  # Cleaner and safer!

Related Issues

Fixes #2744

Acknowledgments

Thanks to the issue author for the excellent write-up and clear use case!


🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Implements the Python context manager protocol (__enter__ / __exit__) for
InferencePipeline to enable safe resource cleanup with the 'with' statement.

## Problem

Currently, InferencePipeline requires manual lifecycle management:

```python
pipeline = InferencePipeline.init(...)
pipeline.start()
pipeline.join()
```

If ANY exception occurs between start() and join(), join() is never reached,
causing resource leaks:
- Orphaned inference and dispatching threads keep running
- ThreadPoolExecutor never shuts down
- Profiling traces are not saved to disk

Users must write verbose try/finally blocks to ensure cleanup, which is
error-prone and boilerplate-heavy.

## Solution

Added __enter__ and __exit__ methods following the same pattern as
VideoFileSink (already in the codebase at sinks.py:547-551).

### Changes

**inference/core/interfaces/stream/inference_pipeline.py**:
- Added __enter__() method returning self
- Added __exit__() method calling terminate() then join()
- Added comprehensive docstrings with usage examples
- Both methods are ~8 lines total

**tests/.../stream/test_context_manager.py**:
- New test suite with 8 test cases covering:
  - Basic context manager protocol
  - Exception handling and propagation
  - Resource cleanup guarantees
  - Typical usage patterns from the issue

## Usage

**Before (manual cleanup - prone to leaks)**:
```python
pipeline = InferencePipeline.init(
    video_reference="./video.mp4",
    model_id="my-model/1",
    on_prediction=my_sink,
)
try:
    pipeline.start()
except Exception:
    pipeline.terminate()
    pipeline.join()
    raise
pipeline.join()
```

**After (automatic cleanup)**:
```python
with InferencePipeline.init(
    video_reference="./video.mp4",
    model_id="my-model/1",
    on_prediction=my_sink,
) as pipeline:
    pipeline.start()
# terminate() + join() called automatically, even on exceptions
```

## Benefits

✅ Prevents resource leaks (threads, thread pools, profiling data)
✅ Cleaner, more Pythonic API
✅ Follows established pattern (VideoFileSink uses same approach)
✅ Backward compatible - existing code continues to work
✅ Comprehensive test coverage

## Testing

All changes covered by test_context_manager.py:
- test_enter_returns_pipeline_instance
- test_exit_calls_terminate_and_join
- test_exit_calls_cleanup_on_exception
- test_context_manager_normal_execution
- test_context_manager_exception_handling
- test_context_manager_typical_usage_pattern
- And more...

Tests use mocking to verify cleanup order and exception handling
without requiring actual video sources or models.

Fixes roboflow#2744

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Kushagra Kanaujia seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@PawelPeczek-Roboflow PawelPeczek-Roboflow left a comment

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.

TMP impediment - CLA

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.

Add context manager (__enter__ / __exit__) support to InferencePipeline

3 participants