# How to Detect and Manage Cyclic Dependencies in ApraPipes Pipeline Configurations

> Learn how to detect and manage cyclic dependencies in ApraPipes pipeline configurations. ApraPipes uses depth-first search to identify and report cycles, preventing pipeline errors.

- Repository: [Apra Labs/aprapipes](https://github.com/apra-labs/aprapipes)
- Tags: how-to-guide
- Published: 2026-02-25

---

**ApraPipes detects cyclic dependencies using a depth-first search with three-color vertex coloring on an adjacency list built from the pipeline's JSON connections array, reporting the exact cycle path as a GRAPH_HAS_CYCLE validation error.**

ApraPipes is an open-source C++ framework for building high-performance multimedia processing pipelines using declarative JSON configurations. Because pipelines are modeled as directed graphs where modules are nodes and connections are edges, **cyclic dependencies** can create deadlocks or infinite processing loops that must be caught before runtime.

## How Cyclic Dependencies Are Detected in ApraPipes

The framework validates pipeline configurations by treating the `connections` array as a directed graph and applying a standard graph cycle detection algorithm implemented in [`base/src/declarative/PipelineValidator.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PipelineValidator.cpp).

### Building the Adjacency List

During validation, the validator constructs an adjacency list where each module ID maps to its outgoing connections. This happens between lines 792-800 in [`PipelineValidator.cpp`](https://github.com/apra-labs/aprapipes/blob/main/PipelineValidator.cpp).

```cpp
// Build adjacency list for cycle detection (PipelineValidator.cpp)
std::map<std::string, std::vector<std::string>> adjacency;
for (const auto& conn : desc.connections) {
    adjacency[conn.from_module].push_back(conn.to_module);
}

```

### Depth-First Search with Three-Color Vertex Coloring

The validator implements a recursive DFS using a color map to track vertex states (lines 805-830). Each module starts as **White (0)** (unvisited), transitions to **Gray (1)** when entering the recursion stack, and becomes **Black (2)** after processing all descendants.

```cpp
// White = 0, Gray = 1, Black = 2
std::map<std::string, int> color;
for (const auto& module : desc.modules) {
    color[module.instance_id] = 0;
}

```

When the algorithm encounters a **Gray** neighbor, it has found a back-edge indicating a cycle. The current path is captured and formatted as a human-readable string (e.g., `A → B → C → A`).

```cpp
if (color[neighbor] == 1) {
    // Found cycle – neighbor is in current path
    path.push_back(neighbor);
    return true;
}

```

### Cycle Reporting and Error Messages

Upon detecting a cycle, the validator creates an `Issue` of type `GRAPH_HAS_CYCLE` with a descriptive message and remediation suggestion (lines 831-845). Only the first detected cycle is reported per validation run.

The error message format includes the exact cycle path:

```

Error [GRAPH_HAS_CYCLE] pipeline: Cycle detected in pipeline: A -> B -> A
Suggestion: Remove one of the connections to break the cycle

```

## Managing and Resolving Cyclic Dependencies

Once detected, cyclic dependencies must be resolved before the pipeline can execute. ApraPipes provides both preventive and corrective mechanisms.

### Design-Time Prevention

While the JSON schema ([`docs/declarative-pipeline/pipeline-schema.json`](https://github.com/apra-labs/aprapipes/blob/main/docs/declarative-pipeline/pipeline-schema.json)) defines the structure of modules and connections, it does not enforce acyclicity at the schema level. The `PipelineValidator` serves as the authoritative gate, rejecting cyclic configurations during the validation phase before any modules are instantiated.

### Breaking Cycles in Existing Configurations

To resolve a detected cycle, modify the pipeline configuration by:

- **Removing one connection** that closes the loop (e.g., delete the edge that creates the back-reference)
- **Re-routing data flow** through an intermediate module such as a buffer or transformer to transform the cyclic graph into a Directed Acyclic Graph (DAG)

### Automated Validation in CI/CD

Integrate the `aprapipes_cli validate` command into continuous integration pipelines to catch cyclic dependencies automatically:

```bash
aprapipes_cli validate pipeline_config.json

# Returns non-zero exit code if GRAPH_HAS_CYCLE or other issues exist

```

This ensures that only valid, acyclic pipeline configurations reach production environments.

## Code Examples

### Valid Acyclic Pipeline Configuration

The following JSON defines a linear processing chain with no cyclic dependencies:

```json
{
  "modules": [
    { "instance_id": "src", "module_type": "FileReaderModule" },
    { "instance_id": "proc", "module_type": "ResizeModule" },
    { "instance_id": "sink", "module_type": "VideoWriterModule" }
  ],
  "connections": [
    { "from_module": "src", "from_port": "output", "to_module": "proc", "to_port": "input" },
    { "from_module": "proc", "from_port": "output", "to_module": "sink", "to_port": "input" }
  ]
}

```

Validation succeeds with no cycle detection triggers:

```bash
aprapipes_cli validate examples/basic/split_pipeline.json

# → Validation successful (no issues)

```

### Cyclic Configuration and Validation Output

Introducing a back-edge creates a cycle between modules `A` and `B`:

```json
{
  "modules": [
    { "instance_id": "A", "module_type": "FileReaderModule" },
    { "instance_id": "B", "module_type": "ResizeModule" }
  ],
  "connections": [
    { "from_module": "A", "from_port": "output", "to_module": "B", "to_port": "input" },
    { "from_module": "B", "from_port": "output", "to_module": "A", "to_port": "input" }
  ]
}

```

The validator detects and reports the specific cycle path:

```bash
aprapipes_cli validate cyclic_example.json
Error [GRAPH_HAS_CYCLE] pipeline: Cycle detected in pipeline: A -> B -> A
Suggestion: Remove one of the connections to break the cycle

```

### Programmatic Detection in C++

For applications that validate pipelines programmatically, use the `PipelineValidator` API directly:

```cpp
#include "aprapipes/base/src/declarative/PipelineValidator.h"

aprapipes::declarative::PipelineDescription desc = loadFromJson("my_pipeline.json");
aprapipes::declarative::PipelineValidator validator;
auto result = validator.validate(desc);

if (!result.issues.empty()) {
    for (const auto& issue : result.issues) {
        if (issue.type == aprapipes::declarative::Issue::GRAPH_HAS_CYCLE) {
            std::cout << "Cycle found: " << issue.message << std::endl;
        }
    }
}

```

## Summary

- **ApraPipes models pipelines as directed graphs** where modules are vertices and connections are edges, requiring acyclicity for valid execution.
- **Cycle detection uses DFS with three-color vertex coloring** (White/Gray/Black) implemented in [`base/src/declarative/PipelineValidator.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PipelineValidator.cpp) (lines 792-845).
- **Validation errors include the exact cycle path** (e.g., `A -> B -> A`) and actionable remediation suggestions.
- **Resolution strategies** include removing back-edges or re-routing data through intermediate modules to form a DAG.
- **Integration** via CLI (`aprapipes_cli validate`) or C++ API ensures cyclic dependencies are caught at design-time before runtime instantiation.

## Frequently Asked Questions

### What algorithm does ApraPipes use to detect cyclic dependencies?

ApraPipes uses a **Depth-First Search (DFS) with three-color vertex coloring** to detect cycles in pipeline configurations. The algorithm assigns each module a color state—White (0) for unvisited, Gray (1) for currently in the recursion stack, and Black (2) for fully processed—and identifies a cycle when it encounters a Gray neighbor, indicating a back-edge in the graph.

### Where is the cycle detection logic implemented in the source code?

The cycle detection logic is implemented in **[`base/src/declarative/PipelineValidator.cpp`](https://github.com/apra-labs/aprapipes/blob/main/base/src/declarative/PipelineValidator.cpp)** between lines 792 and 845. This section contains the adjacency list construction, color map initialization, recursive DFS traversal, and the `GRAPH_HAS_CYCLE` error reporting mechanism that validates pipeline configurations before runtime execution.

### How does ApraPipes report cyclic dependency errors?

When a cycle is detected, ApraPipes generates a validation `Issue` of type `GRAPH_HAS_CYCLE` containing a human-readable message that specifies the exact cycle path (e.g., `A -> B -> C -> A`). The error includes a suggestion to remove one of the connections to break the cycle, and the CLI returns a non-zero exit code to signal validation failure in automated workflows.

### Can cyclic dependencies be prevented at the JSON schema level?

No, the JSON schema defined in [`docs/declarative-pipeline/pipeline-schema.json`](https://github.com/apra-labs/aprapipes/blob/main/docs/declarative-pipeline/pipeline-schema.json) validates the structure of modules and connections but cannot enforce graph acyclicity. The `PipelineValidator` serves as the authoritative gatekeeper that performs the computational validation required to ensure the pipeline forms a Directed Acyclic Graph (DAG) before instantiation.