Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StackOverflowException in binding #855

Closed
donandren opened this issue Jan 14, 2017 · 3 comments
Closed

StackOverflowException in binding #855

donandren opened this issue Jan 14, 2017 · 3 comments

Comments

@donandren
Copy link
Contributor

donandren commented Jan 14, 2017

StackOverflowException in binding when property is changed in setter before raise property changed.
That's probably the root reason for problem in #824

Example if you bind a listbox selected Item to property selected item and property is like:

            set
            {
                if (_selectedItem != value)
                {
                    int index = Items.IndexOf(value);
                    if (index > 1)//or some other logic that's changing the current value
                    {
                        _selectedItem = Items[index - 1];
                    }
                    else
                    {
                        _selectedItem = value;
                        
                    }
                    this.RaisePropertyChanged();
                }
            }
donandren added a commit to donandren/Avalonia that referenced this issue Jan 14, 2017
@donandren
Copy link
Contributor Author

donandren commented Jan 14, 2017

easy repros can be found in branch https://github.com/donandren/Avalonia/tree/issues/855

Repro steps:
1.Start Virtualization Application
2.Select Item after the second and StackOverflowException will occur

Another repro added (covering also #824):
1.Start Virtualization Application
2.Try change Slider value and StackOverflowException will occur

donandren added a commit to donandren/Avalonia that referenced this issue Jan 14, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Jan 15, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Jan 15, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 16, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 16, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 16, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 16, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
… behind scenes is using weakobservable
donandren added a commit to donandren/Avalonia that referenced this issue Feb 26, 2017
donandren added a commit to donandren/Avalonia that referenced this issue Jul 6, 2017
@jkoritzinsky
Copy link
Collaborator

I've finally figured out why this causes a stack overflow. Below is the order of events that cause it.

Objects required: A, B are AvaloniaObjects, and C is an object that implements INPC and changes the current value before setting setting it and raising PropertyChanged.

Two-way bind A.Prop <---> C.Prop. Then bind B.Prop <---> A.Prop.

Order of events:

  1. Set a value on A.Prop that C.Prop will change before it sets. Lets call this value X.
  2. A raises AvaloniaPropertyChanged event for Prop with value X
  3. A sends an update to its binding to update to value X.
    a. Value X is set on C.
    b. C changes value X to value Y before raising PropertyChanged
    c. C raises PropertyChanged after setting Prop to Y
    d. Because A.Prop <--> C.Prop is two-way, A.Prop is set to Y
    e. Because B.Prop <--> A.Prop is two-way, B.Prop is set to Y
    f. At this point, A.Prop, B.Prop, C.Prop all equal Y.
  4. Because B.Prop <--> A.Prop is two-way, B.Prop is set to X
  5. Because B.Prop <--> A.Prop is two-way, A.Prop is set to X
  6. Go to step 2.

Now I have no idea how to fix this, but at least we have the execution path to work with.

@jkoritzinsky
Copy link
Collaborator

jkoritzinsky commented Oct 13, 2017

In this case it looks like we can just make B update A before C, since that will lead us to a steady-state. However, that does not work in all cases. Let's look at the situation below:

Objects required: A, B are AvaloniaObjects, and C is an object that implements INPC and changes the current value before setting setting it and raising PropertyChanged.

Two-way bind A.Prop <---> C.Prop. Then bind B.Prop <---> A.Prop.

Order of events:

  1. Set a value on A.Prop that C.Prop will change before it sets and B.Prop will change before it sets (via a validate function). Lets call this value X.
  2. A raises AvaloniaPropertyChanged event for Prop with value X
  3. A sends an update to its binding to update to value X.
  4. Value X is set on C.
  5. C changes the value to Y before raising PropertyChanged
  6. C raises PropertyChanged after setting Prop to Y
  7. Because A.Prop <--> C.Prop is two-way, A.Prop is set to Y
  8. Because B.Prop <--> A.Prop is two-way, B.Prop transforms the value to Z and sets itself to Z.
  9. Because B.Prop <--> A.Prop is two-way, A.Prop is set to Z.
  10. Because A.Prop <---> C.Prop is two-way, Value Z is set on C.Prop.
  11. Go to step 5

In this situation, we never hit a steady-state so there is no good way to resolve this stack overflow without making the bindings inconsistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants