Signal Handling & Cancellation¶
Handle workflow cancellation and SIGTERM/SIGINT signals gracefully.
Overview¶
The CancellationHandler class provides unified handling for managing cancellation signals in GitHub Actions workflows, allowing you to clean up resources and exit gracefully when a workflow is cancelled.
API Reference¶
CancellationHandler()¶
Creates a new cancellation handler instance.
Example:
from github_action_toolkit import CancellationHandler
handler = CancellationHandler()
enable()¶
Enable automatic handling of cancellation signals (SIGTERM and SIGINT). When enabled, cancellation signals will:
Call all registered cancellation handlers
Raise a
CancellationRequestedexception
example:
from github_action_toolkit import CancellationHandler
from github_action_toolkit.exceptions import CancellationRequested
handler = CancellationHandler()
handler.enable()
try:
# Your long-running operation
process_data()
except CancellationRequested:
print("Operation was cancelled, cleaning up...")
finally:
handler.disable()
disable()¶
Disable automatic handling of cancellation signals and restore original signal handlers.
example:
from github_action_toolkit import CancellationHandler
handler = CancellationHandler()
handler.enable()
# ... do work ...
handler.disable()
is_enabled()¶
Check if cancellation support is currently enabled for this handler.
example:
from github_action_toolkit import CancellationHandler
handler = CancellationHandler()
if handler.is_enabled():
print("Cancellation support is active")
register(handler_func)¶
Register a cleanup handler to be called when cancellation is requested. Handlers are called in the order they were registered.
example:
from github_action_toolkit import CancellationHandler
def cleanup_database():
print("Closing database connections...")
# Cleanup code
def cleanup_files():
print("Removing temporary files...")
# Cleanup code
cancellation = CancellationHandler()
cancellation.register(cleanup_database)
cancellation.register(cleanup_files)
cancellation.enable()
# Both handlers will be called on cancellation
CancellationRequested Exception¶
Exception raised when a cancellation signal (SIGTERM or SIGINT) is received. Catch this exception to handle cancellation gracefully.
Import from github_action_toolkit.exceptions:
from github_action_toolkit.exceptions import CancellationRequested
example:
from github_action_toolkit import CancellationRequested, enable_cancellation_support
import time
enable_cancellation_support()
try:
for i in range(100):
print(f"Processing item {i}...")
time.sleep(1)
except CancellationRequested as e:
print(f"Cancelled: {e}")
# Perform cleanup
save_progress(i)
Complete Example¶
Here’s a complete example showing how to use cancellation support in a GitHub Action:
from github_action_toolkit import (
enable_cancellation_support,
register_cancellation_handler,
CancellationRequested,
info,
warning,
)
import time
# Track progress
progress_file = "/tmp/progress.txt"
def save_progress(current_item):
"""Save current progress for resumption"""
with open(progress_file, "w") as f:
f.write(str(current_item))
info(f"Progress saved: {current_item}")
def cleanup():
"""Cleanup handler called on cancellation"""
warning("Cleaning up resources...")
# Close connections, save state, etc.
# Set up cancellation support
register_cancellation_handler(cleanup)
enable_cancellation_support()
try:
info("Starting long-running operation...")
# Your main workflow logic
for i in range(1000):
info(f"Processing item {i}")
time.sleep(0.1)
# Periodically save progress
if i % 100 == 0:
save_progress(i)
info("Operation completed successfully")
except CancellationRequested as e:
warning(f"Operation cancelled: {e}")
# Save final progress before exit
save_progress(i)
exit(1)
except Exception as e:
error(f"Operation failed: {e}")
exit(1)
Best Practices¶
Always Enable Early¶
Enable cancellation support at the start of your action, before any long-running operations:
enable_cancellation_support()
# Rest of your code
Register Cleanup Handlers¶
Register handlers for any resources that need cleanup:
register_cancellation_handler(close_database_connections)
register_cancellation_handler(remove_temp_files)
register_cancellation_handler(stop_background_processes)
Save Progress¶
For long-running operations, periodically save progress so work can be resumed:
try:
for item in large_dataset:
process(item)
if should_save_checkpoint():
save_progress()
except CancellationRequested:
save_progress() # Final save before exit
Handle Exceptions in Handlers¶
Cancellation handlers should be defensive and catch their own exceptions:
def cleanup_handler():
try:
close_connections()
except Exception as e:
print(f"Error during cleanup: {e}")
# Continue with other cleanup
Don’t Block in Handlers¶
Keep cancellation handlers fast. Avoid long-running operations that would delay shutdown:
def quick_cleanup():
# Good: Fast cleanup
close_file_handles()
def slow_cleanup():
# Bad: Blocks shutdown
for i in range(1000):
process_item(i)