Skip to content

Commit fd083aa

Browse files
Bugfix callback graph with circular callbacks by adding visited markers
1 parent 0221a97 commit fd083aa

File tree

2 files changed

+14
-7
lines changed

2 files changed

+14
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
1616
- [#3465](https://github.com/plotly/dash/pull/3465) Plotly cloud integrations, add devtool API, placeholder plotly cloud CLI & publish button, `dash[cloud]` extra dependencies.
1717

1818
## Fixed
19+
- [#3490](https://github.com/plotly/dash/pull/3490) Fix stack overflow when circular callbacks are displayed on the devtool callback
1920
- [#3395](https://github.com/plotly/dash/pull/3395) Fix Components added through set_props() cannot trigger related callback functions. Fix [#3316](https://github.com/plotly/dash/issues/3316)
2021
- [#3415](https://github.com/plotly/dash/pull/3415) Fix the error triggered when only a single no_update is returned for client-side callback functions with multiple Outputs. Fix [#3366](https://github.com/plotly/dash/issues/3366)
2122
- [#3416](https://github.com/plotly/dash/issues/3416) Fix DeprecationWarning in dash/_jupyter.py by migrating from deprecated ipykernel.comm.Comm to comm module

dash/dash-renderer/src/components/error/CallbackGraph/CallbackGraphEffects.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ function getEdgeTypes(node) {
2727
* @returns {function} - cleanup function, for useEffect hook
2828
*/
2929
export function updateSelectedNode(cy, id) {
30-
function ascend(node, collection) {
30+
function ascend(node, collection, visited = new Set()) {
3131
// FIXME: Should we include State parents but non-recursively?
32+
if (visited.has(node.id())) {
33+
return;
34+
}
35+
visited.add(node.id());
3236
const type = node.data().type === 'callback' ? 'input' : 'output';
3337
const edges = getEdgeTypes(node)[type];
3438
const parents = edges.sources();
@@ -37,10 +41,14 @@ export function updateSelectedNode(cy, id) {
3741
if (node.data().type === 'property') {
3842
collection.merge(node.ancestors());
3943
}
40-
parents.forEach(node => ascend(node, collection));
44+
parents.forEach(node => ascend(node, collection, visited));
4145
}
4246

43-
function descend(node, collection) {
47+
function descend(node, collection, visited = new Set()) {
48+
if (visited.has(node.id())) {
49+
return;
50+
}
51+
visited.add(node.id());
4452
const type = node.data().type === 'callback' ? 'output' : 'input';
4553
const edges = getEdgeTypes(node)[type];
4654
const children = edges.targets();
@@ -49,7 +57,7 @@ export function updateSelectedNode(cy, id) {
4957
if (node.data().type === 'property') {
5058
collection.merge(node.ancestors());
5159
}
52-
children.forEach(node => descend(node, collection));
60+
children.forEach(node => descend(node, collection, visited));
5361
}
5462

5563
if (id) {
@@ -62,9 +70,7 @@ export function updateSelectedNode(cy, id) {
6270
// Find the subtree that the node belongs to. A subtree contains
6371
// all all ancestors and descendants that are connected via Inputs
6472
// or Outputs (but not State).
65-
66-
// WARNING: No cycle detection!
67-
73+
6874
const subtree = cy.collection();
6975
subtree.merge(node);
7076
ascend(node, subtree);

0 commit comments

Comments
 (0)