Context propagation with graceful cancellation (bd-rtp, bd-yb8, bd-2o2)

Complete implementation of signal-aware context propagation for graceful
cancellation across all commands and storage operations.

Key changes:

1. Signal-aware contexts (bd-rtp):
   - Added rootCtx/rootCancel in main.go using signal.NotifyContext()
   - Set up in PersistentPreRun, cancelled in PersistentPostRun
   - Daemon uses same pattern in runDaemonLoop()
   - Handles SIGINT/SIGTERM for graceful shutdown

2. Context propagation (bd-yb8):
   - All commands now use rootCtx instead of context.Background()
   - sqlite.New() receives context for cancellable operations
   - Database operations respect context cancellation
   - Storage layer propagates context through all queries

3. Cancellation tests (bd-2o2):
   - Added import_cancellation_test.go with comprehensive tests
   - Added export cancellation test in export_test.go
   - Tests verify database integrity after cancellation
   - All cancellation tests passing

Fixes applied during review:
   - Fixed rootCtx lifecycle (removed premature defer from PersistentPreRun)
   - Fixed test context contamination (reset rootCtx in test cleanup)
   - Fixed export tests missing context setup

Impact:
   - Pressing Ctrl+C during import/export now cancels gracefully
   - No database corruption or hanging transactions
   - Clean shutdown of all operations

Tested:
   - go build ./cmd/bd ✓
   - go test ./cmd/bd -run TestImportCancellation ✓
   - go test ./cmd/bd -run TestExportCommand ✓
   - Manual Ctrl+C testing verified

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-11-20 21:57:23 -05:00
parent 91c684cdbe
commit 57253f93a3
72 changed files with 387 additions and 232 deletions

View File

@@ -2,7 +2,6 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
@@ -28,7 +27,7 @@ var depAddCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
depType, _ := cmd.Flags().GetString("type")
ctx := context.Background()
ctx := rootCtx
// Resolve partial IDs first
var fromID, toID string
@@ -155,7 +154,7 @@ var depRemoveCmd = &cobra.Command{
Short: "Remove a dependency",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
ctx := rootCtx
// Resolve partial IDs first
var fromID, toID string
@@ -252,7 +251,7 @@ var depTreeCmd = &cobra.Command{
Short: "Show dependency tree",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
ctx := rootCtx
// Resolve partial ID first
var fullID string
@@ -279,7 +278,7 @@ var depTreeCmd = &cobra.Command{
// If daemon is running but doesn't support this command, use direct storage
if daemonClient != nil && store == nil {
var err error
store, err = sqlite.New(dbPath)
store, err = sqlite.New(rootCtx, dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err)
os.Exit(1)
@@ -365,7 +364,7 @@ var depCyclesCmd = &cobra.Command{
// If daemon is running but doesn't support this command, use direct storage
if daemonClient != nil && store == nil {
var err error
store, err = sqlite.New(dbPath)
store, err = sqlite.New(rootCtx, dbPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open database: %v\n", err)
os.Exit(1)
@@ -373,7 +372,7 @@ var depCyclesCmd = &cobra.Command{
defer func() { _ = store.Close() }()
}
ctx := context.Background()
ctx := rootCtx
cycles, err := store.DetectCycles(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)