-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Fix #2946: Ensure invalidation on freezeReactiveValue(input) freeze and new value #3055
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
c078e32
to
48da17f
Compare
OK. This PR now represents sort of a compromise, in an effort to fix the problems we know are actually happening to people, without attempting to fix problems that are theoretical and may not affect anyone (because attempting to fix the latter could conceivably break compatibility if people have figured out clever ways to make use of freeze/thaw).
Future work:
|
wch
reviewed
Sep 18, 2020
wch
reviewed
Sep 18, 2020
jcheng5
commented
Sep 22, 2020
1. freezeReactiveValue(input, "x") is called, inside a renderUI or in an observer that then calls updateXXXInput 2. Some reactive output tries to access input$x, this takes a reactive dependency but throws a (silent) error 3. When the flush cycle ends, it automatically thaws What's *supposed* to happen next is the client receives the new UI or updateXXXInput message, which causes input$x to change, which causes the reactive output to invalidate and re-run, this time without input$x being frozen. This works, except when the renderUI or updateXXXInput just so happens to set input$x to the same value it already is. In this case, the client would detect the duplicate value and not send it to the server. Therefore, the reactive output would not be invalidated, and effectively be "stalled" until the next time it is invalidated for some other reason. With this change, freezeReactiveValue(input, "x") has a new side effect, which is telling the client that the very next update to input$x should not undergo duplicate checking.
…Values(non-input), but warn We don't think anyone is using the freeze functions in the ways that we are deprecating, if so they should contact us via the link provided. If it turns out nobody complains, we can remove the problematic functions. If people complain, then we'll find out what they're using them for and we can fix them properly.
This brings it into line with all of the other input bindings. The only exception is sliderInput, which has a more complicated codepath that goes out of its way to force the slider, for its own reasons; I didn't change the slider for fear of breaking something, and it also doesn't exhibit the problem I'm here to fix (next paragraph). The goal is to ensure that if forgetLastInput is called on an input, and then that input receives a message (updateXXXInput) to update its value, BUT the new value is the SAME as its existing value, that the input binding still acts like something changed. This is because we need the id/value to go through the InputSender code path, and alert the server if a previously frozen input is now thawed.
bf9f3b7
to
980a1e5
Compare
jcheng5
added a commit
to rstudio/shinycoreci-apps
that referenced
this pull request
Oct 7, 2020
Comments to come
jcheng5
added a commit
to rstudio/shinycoreci-apps
that referenced
this pull request
Oct 7, 2020
There are three scenarios described here: rstudio/shiny#3055 App 207-freeze-invalidate tests Bug 1 and 3.
wch
approved these changes
Oct 7, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The goal of
freezeReactiveValue
is to protect reactives, observers, and (especially) outputs from seeing temporarily inconsistent states due to asynchronous operations that need to complete before the state can become consistent.What is an inconsistent state?
Consider an app with two inputs:
input$dataset
andinput$columns
.The
columns
selector should show the users the columns available from theinput$dataset
dataset. To maintain this relationship, we use an observer.And here's an output that wants to use the dataset and column.
The problem occurs when
input$dataset
changes (also on first load, but let's focus on the change case), say frompressure
tocars
. The session updatesinput$dataset
tocars
, but the value ofinput$column
is going to be a column frompressure
still. So therenderPrint
is going to try to accesscars[,"temperature"]
which will cause an error.A moment later, the
updateSelectInput
takes effect in the client, causinginput$column
to be updated.renderPrint
runs again, causing the error to disappear and a successful render to happen in its place.The goal of
freezeReactiveValue
is to get rid of the flash.The current implementation is supposed to work like this:
freezeReactiveValue
. This sets afrozen
flag; while this flag is set, reads to the reactive value in question will throw the equivalent ofreq(FALSE)
.updateTextInput
or renders a UI containing the same input or whatever.frozen
values are automatically thawed.However, there are several issues in the current (CRAN) implementation that make it subtly unreliable.
Bug 1: Ordering between updater and consumer matters
The first problem is that
freezeReactiveValue
doesn't ever trigger invalidation. If a consumer happens to run before an updater, then it can get into an error state, and freezing the reactive value afterwards won't notify the consumer. This is mostly a problem for outputs: it's not too bad for outputs to be in error states temporarily, but what's bad is for that error to make its way to the client.input$dataset = "cars"
output$summary
executes, resulting in an errorfreezeReactiveValue()
output$summary
re-executes, resulting inreq(FALSE)
Ideally, updaters/outputs that call
freezeReactiveValue
would be set to a higher observer priority than any consumers. That would cause this not to be a problem at all, and it's also necessary to do this for non-output observers anyway (since an error can potentially be more serious in those cases).Repro:
Change the dataset and an error flashes by. I'm using
priority = -1
to make the problem a little more persistent, but it can occur without it.The fix to this problem is to invalidate upon
freezeReactiveValue
. Possible downsides are that existing apps could have consumers that either expensively or side-effect-ily execute after the invalidation occurs, i.e. they do slow or side-effecty stuff before attempting to read the frozen value.With that fix, you still get an error in the R console but the user never sees the error in the browser.
Bug 2: Invalidation upon completion is unreliable due to dedupe
Similar scenario to last time, but we'll avoid Bug 1 and then go a little further.
input$dataset = "cars"
freezeReactiveValue()
andupdateSelectInput
output$summary
executes, resulting in areq(FALSE)
output$summary
executes, resulting in successThe bug here is that step 5 can break down if
updateSelectInput
just so happens to set the input value to the same value it already has. In Shiny v1.5.0, that's a total no-op as far as the server is concerned.Repro:
Change the dataset from "pressure" to "pressure1" and you'll see the summary go blank.
The fix for this is that calling
freezeReactiveValue
should disable "ignore duplicate values" for the very next set. This needs to be implemented one way forinput
, another way forreactiveValues
, and a third way forreactiveVal
.Bug 3: #2946 Downstream reactive expressions don't react to their dependencies being frozen
The issue discusses it pretty well. Basically, invalidation on freeze is needed--same fix as for bug 1.