Skip to content

Commit 899a665

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

File tree

2 files changed

+13
-7
lines changed

2 files changed

+13
-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: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ function getEdgeTypes(node) {
2727
* @returns {function} - cleanup function, for useEffect hook
2828
*/
2929
export function updateSelectedNode(cy, id) {
30-
function ascend(node, collection) {
31-
// FIXME: Should we include State parents but non-recursively?
30+
function ascend(node, collection, visited = new Set()) {
31+
if (visited.has(node.id())) {
32+
return;
33+
}
34+
visited.add(node.id());
3235
const type = node.data().type === 'callback' ? 'input' : 'output';
3336
const edges = getEdgeTypes(node)[type];
3437
const parents = edges.sources();
@@ -37,10 +40,14 @@ export function updateSelectedNode(cy, id) {
3740
if (node.data().type === 'property') {
3841
collection.merge(node.ancestors());
3942
}
40-
parents.forEach(node => ascend(node, collection));
43+
parents.forEach(node => ascend(node, collection, visited));
4144
}
4245

43-
function descend(node, collection) {
46+
function descend(node, collection, visited = new Set()) {
47+
if (visited.has(node.id())) {
48+
return;
49+
}
50+
visited.add(node.id());
4451
const type = node.data().type === 'callback' ? 'output' : 'input';
4552
const edges = getEdgeTypes(node)[type];
4653
const children = edges.targets();
@@ -49,7 +56,7 @@ export function updateSelectedNode(cy, id) {
4956
if (node.data().type === 'property') {
5057
collection.merge(node.ancestors());
5158
}
52-
children.forEach(node => descend(node, collection));
59+
children.forEach(node => descend(node, collection, visited));
5360
}
5461

5562
if (id) {
@@ -63,8 +70,6 @@ export function updateSelectedNode(cy, id) {
6370
// all all ancestors and descendants that are connected via Inputs
6471
// or Outputs (but not State).
6572

66-
// WARNING: No cycle detection!
67-
6873
const subtree = cy.collection();
6974
subtree.merge(node);
7075
ascend(node, subtree);

0 commit comments

Comments
 (0)