|
| 1 | +// Copyright 2025 The Cockroach Authors. |
| 2 | +// |
| 3 | +// Use of this software is governed by the CockroachDB Software License |
| 4 | +// included in the /LICENSE file. |
| 5 | + |
| 6 | +package inspect |
| 7 | + |
| 8 | +import ( |
| 9 | + "context" |
| 10 | + |
| 11 | + "github.com/cockroachdb/cockroach/pkg/roachpb" |
| 12 | + "github.com/cockroachdb/cockroach/pkg/sql/execinfra" |
| 13 | + "github.com/cockroachdb/errors" |
| 14 | +) |
| 15 | + |
| 16 | +// inspectCheck defines a single validation operation used by the INSPECT system. |
| 17 | +// Each check represents a specific type of data validation, such as index consistency. |
| 18 | +// |
| 19 | +// A check is stateful. It must be initialized with Start(), which prepares it to |
| 20 | +// produce results. Once started, repeated calls to Next() yield zero or more |
| 21 | +// inspectIssue results until Done() returns true. After completion, Close() releases |
| 22 | +// any associated resources. |
| 23 | +// |
| 24 | +// Checks are expected to run on a single node and may execute SQL queries or scans |
| 25 | +// under the hood to detect inconsistencies. All results are surfaced through the |
| 26 | +// inspectIssue type. |
| 27 | +type inspectCheck interface { |
| 28 | + // Started reports whether the check has been initialized. |
| 29 | + Started() bool |
| 30 | + |
| 31 | + // Start prepares the check to begin returning results. |
| 32 | + Start(ctx context.Context, cfg *execinfra.ServerConfig, span roachpb.Span, workerIndex int) error |
| 33 | + |
| 34 | + // Next returns the next inspect error, if any. |
| 35 | + // Returns (nil, nil) when there are no errors for the current row. |
| 36 | + Next(ctx context.Context, cfg *execinfra.ServerConfig) (*inspectIssue, error) |
| 37 | + |
| 38 | + // Done reports whether the check has produced all results. |
| 39 | + Done(ctx context.Context) bool |
| 40 | + |
| 41 | + // Close cleans up resources for the check. |
| 42 | + Close(ctx context.Context) error |
| 43 | +} |
| 44 | + |
| 45 | +// inspectLogger records issues found by inspect checks. Implementations of this |
| 46 | +// interface define how inspectIssue results are handled. |
| 47 | +type inspectLogger interface { |
| 48 | + logIssue(ctx context.Context, issue *inspectIssue) error |
| 49 | +} |
| 50 | + |
| 51 | +// inspectRunner coordinates the execution of a set of inspectChecks. |
| 52 | +// |
| 53 | +// It manages the lifecycle of each check, including initialization, |
| 54 | +// iteration, and cleanup. Each call to Step processes one unit of |
| 55 | +// work: either advancing a check by one result or moving on to the next |
| 56 | +// check if the current one is finished. |
| 57 | +// |
| 58 | +// When a validation issue is found, the runner calls the provided |
| 59 | +// inspectLogger to record it. |
| 60 | +type inspectRunner struct { |
| 61 | + // checks holds the list of checks to run. Each check is run to completion |
| 62 | + // before moving on to the next. |
| 63 | + checks []inspectCheck |
| 64 | + |
| 65 | + // logger records issues reported by the checks. |
| 66 | + logger inspectLogger |
| 67 | +} |
| 68 | + |
| 69 | +// Step advances execution by processing one result from the current inspectCheck. |
| 70 | +// |
| 71 | +// If the current check is not yet started, it is initialized. If it has more results, |
| 72 | +// Step retrieves the next result and logs it if an issue is found. If the check is done, |
| 73 | +// it is closed and removed from the queue. |
| 74 | +// |
| 75 | +// Returns true if a check was advanced or an issue was found. Returns false when all |
| 76 | +// checks are complete. If an error occurs at any stage, it is returned immediately. |
| 77 | +func (c *inspectRunner) Step( |
| 78 | + ctx context.Context, cfg *execinfra.ServerConfig, span roachpb.Span, workerIndex int, |
| 79 | +) (bool, error) { |
| 80 | + for len(c.checks) > 0 { |
| 81 | + check := c.checks[0] |
| 82 | + if !check.Started() { |
| 83 | + if err := check.Start(ctx, cfg, span, workerIndex); err != nil { |
| 84 | + return false, err |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + if !check.Done(ctx) { |
| 89 | + issue, err := check.Next(ctx, cfg) |
| 90 | + if err != nil { |
| 91 | + return false, err |
| 92 | + } |
| 93 | + if issue != nil { |
| 94 | + err = c.logger.logIssue(ctx, issue) |
| 95 | + if err != nil { |
| 96 | + return false, errors.Wrapf(err, "error logging inspect issue") |
| 97 | + } |
| 98 | + } |
| 99 | + return true, nil |
| 100 | + } |
| 101 | + |
| 102 | + if err := check.Close(ctx); err != nil { |
| 103 | + return false, err |
| 104 | + } |
| 105 | + c.checks = c.checks[1:] |
| 106 | + } |
| 107 | + return false, nil |
| 108 | +} |
0 commit comments