mobxjs / mobx

Simple, scalable state management.
http://mobx.js.org
MIT License
27.43k stars 1.76k forks source link

Reaction racecondition #1318

Closed bedeho closed 6 years ago

bedeho commented 6 years ago

I have tried to illustrate a situation I am running into in the way I have constructed my MOBX models, the figure is pretty straight forward.

mobx_problem

The important constraint here is that there is an invariant over the value of A and D.

The problem I am running into is that reaction, when the initial action mutates A, reaction B ends up seeing an inconsistent view of observable A and variable D, violating this invariant. This despite the fact that A is mutated through an action.

Now there at least two simplex fixes here

a) have reaction B instead track some new observable X inside the user interface store, and have the reaction C update X and C in an action.

b) move the updating of D inside of the action which updates A, making dispatching of B wait until both A and D are in synch.

The fact that I am even having to think of this is making me suspect that I am some how working against the recommended patterns for MOBX, as it would eventually get really out of hand to manage these sorts of possible race conditions.

What am I doing wrong here? Is there a way to organize MOBX projects where rules out the possibility of such race conditions by design, rather than fixing them ex-post in an ad-hoc way?

mweststrate commented 6 years ago

Reactions shouldn't be used to update values based on other values. Reactions are for side effects like networking, flushing UI updates etc. Use computed values to derive new values from existing values. Computed values will run in the most optimal order according to the dependency tree.

If I understand the diagram correctly, reaction C can just be eliminated and variable D can be express as computed, which will make sure B will only run after D has been recomputed, and not before or twice.

bedeho commented 6 years ago

Thank you for the prompt response

1) if D is computed based on observing A, then wouldn't still reaction B and that computation happen in order after A is observed, leading to the same inconsistency?

Perhaps I am misunderstanding how actions work, but my understanding was that all state is mutated - without running any reactions/computations until the (outer) action is done. In this case, the only action is the one mutating A. The two reactions will still run in order? Hence my suggestion b) ?.

2) The variable D is set to a new object, type

@computed get() {
 return new Foo(domainStore.A)
}

is that considered good practice to do?

3) there is also no need to have D as observable, which would be the case if it was computed.

mweststrate commented 6 years ago

Please take the effort to read the documentation a bit more careful, I think these things are all explained in the docs :)

But yes, all computed values are observable as well. And the order will be consistent because, as said, mobx tracks the depency tree.

So it will determine that

  1. D depends on A
  2. D depends on B
  3. B depends on A
  4. hence conclude that the correct execution order is A => B => D, removing your potential inconsistency.

Op di 23 jan. 2018 om 10:26 schreef Bedeho Mender <notifications@github.com

:

Thank you for the prompt response

  1. if D is computed based on observing A, then wouldn't still reaction B and that computation happen in order after A is observed, leading to the same inconsistency?

Perhaps I am misunderstanding how actions work, but my understanding was that all state is mutated - without running any reactions/computations until the (outer) action is done. In this case, the only action is the one mutating A. The two reactions will still run in order? Hence my suggestion b) ?.

  1. The variable D is set to a new object, type

@computed get() { return new Foo(domainStore.A) }

is that considered good practice to do?

  1. there is also no need to have D as observable, which would be the case if it was computed.

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/1318#issuecomment-359730579, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhF7cMVycTQ0qi-phaO3Ce1kebGjRks5tNaXNgaJpZM4RpSFx .

mweststrate commented 6 years ago

Sorry, confused B & D in the above post. They should be swapped everywhere

Op di 23 jan. 2018 om 10:29 schreef Michel Weststrate <mweststrate@gmail.com

:

Please take the effort to read the documentation a bit more careful, I think these things are all explained in the docs :)

But yes, all computed values are observable as well. And the order will be consistent because, as said, mobx tracks the depency tree.

So it will determine that

  1. D depends on A
  2. D depends on B
  3. B depends on A
  4. hence conclude that the correct execution order is A => B => D, removing your potential inconsistency.

Op di 23 jan. 2018 om 10:26 schreef Bedeho Mender < notifications@github.com>:

Thank you for the prompt response

  1. if D is computed based on observing A, then wouldn't still reaction B and that computation happen in order after A is observed, leading to the same inconsistency?

Perhaps I am misunderstanding how actions work, but my understanding was that all state is mutated - without running any reactions/computations until the (outer) action is done. In this case, the only action is the one mutating A. The two reactions will still run in order? Hence my suggestion b) ?.

  1. The variable D is set to a new object, type

@computed get() { return new Foo(domainStore.A) }

is that considered good practice to do?

  1. there is also no need to have D as observable, which would be the case if it was computed.

— You are receiving this because you commented.

Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/1318#issuecomment-359730579, or mute the thread https://github.com/notifications/unsubscribe-auth/ABvGhF7cMVycTQ0qi-phaO3Ce1kebGjRks5tNaXNgaJpZM4RpSFx .

mweststrate commented 6 years ago

See https://medium.com/@mweststrate/becoming-fully-reactive-an-in-depth-explanation-of-mobservable-55995262a254 where the above is explained in more detail, or this talk: https://www.youtube.com/watch?v=TfxfRkNCnmk

bedeho commented 6 years ago

Thank you for those links. I think I now understand my original misunderstanding of your suggestion.

I was presuming that computation would not be invoked inside the action, but rather they were treated as any other sort of reaction tracking A.

Thanks!