Skip to content

Commit db9e56d

Browse files
authored
Merge pull request rstudio#1768 from rstudio/wch-fix-with-private-seed
Fix withPrivateSeed
2 parents 74c7be0 + e527af1 commit db9e56d

File tree

5 files changed

+55
-42
lines changed

5 files changed

+55
-42
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ Depends:
6464
methods
6565
Imports:
6666
utils,
67-
httpuv (>= 1.3.3),
67+
httpuv (>= 1.3.5),
6868
mime (>= 0.3),
6969
jsonlite (>= 0.9.16),
7070
xtable,

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ shiny 1.0.3.9001
3939

4040
* Fixed [#1755](https://github.com/rstudio/shiny/issues/1755): dynamic htmlwidgets sent the path of the package on the server to the client. ([#1756](https://github.com/rstudio/shiny/pull/1756))
4141

42+
* Fixed [#1763](https://github.com/rstudio/shiny/issues/1763): Shiny's private random stream leaked out into the main random stream. ([#1768](https://github.com/rstudio/shiny/pull/1768))
43+
4244
### Library updates
4345

4446

R/globals.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# R's lazy-loading package scheme causes the private seed to be cached in the
66
# package itself, making our PRNG completely deterministic. This line resets
77
# the private seed during load.
8-
withPrivateSeed(reinitializeSeed())
8+
withPrivateSeed(set.seed(NULL))
99
}
1010

1111
.onAttach <- function(libname, pkgname) {

R/utils.R

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -43,53 +43,43 @@ repeatable <- function(rngfunc, seed = stats::runif(1, 0, .Machine$integer.max))
4343
}
4444
}
4545

46-
# Temporarily set x in env to value, evaluate expr, and
47-
# then restore x to its original state
48-
withTemporary <- function(env, x, value, expr, unset = FALSE) {
49-
50-
if (exists(x, envir = env, inherits = FALSE)) {
51-
oldValue <- get(x, envir = env, inherits = FALSE)
52-
on.exit(
53-
assign(x, oldValue, envir = env, inherits = FALSE),
54-
add = TRUE)
46+
.globals$ownSeed <- NULL
47+
# Evaluate an expression using Shiny's own private stream of
48+
# randomness (not affected by set.seed).
49+
withPrivateSeed <- function(expr) {
50+
# Save the old seed if present.
51+
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
52+
hasOrigSeed <- TRUE
53+
origSeed <- .GlobalEnv$.Random.seed
5554
} else {
56-
on.exit(
57-
rm(list = x, envir = env, inherits = FALSE),
58-
add = TRUE
59-
)
55+
hasOrigSeed <- FALSE
6056
}
6157

62-
if (!missing(value) && !isTRUE(unset))
63-
assign(x, value, envir = env, inherits = FALSE)
64-
else {
65-
if (exists(x, envir = env, inherits = FALSE))
66-
rm(list = x, envir = env, inherits = FALSE)
58+
# Swap in the private seed.
59+
if (is.null(.globals$ownSeed)) {
60+
if (hasOrigSeed) {
61+
# Move old seed out of the way if present.
62+
rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
63+
}
64+
} else {
65+
.GlobalEnv$.Random.seed <- .globals$ownSeed
6766
}
68-
force(expr)
69-
}
7067

71-
.globals$ownSeed <- NULL
72-
# Evaluate an expression using Shiny's own private stream of
73-
# randomness (not affected by set.seed).
74-
withPrivateSeed <- function(expr) {
75-
withTemporary(.GlobalEnv, ".Random.seed",
76-
.globals$ownSeed, unset=is.null(.globals$ownSeed), {
77-
tryCatch({
78-
expr
79-
}, finally = {
80-
.globals$ownSeed <- getExists('.Random.seed', 'numeric', globalenv())
81-
})
68+
# On exit, save the modified private seed, and put the old seed back.
69+
on.exit({
70+
.globals$ownSeed <- .GlobalEnv$.Random.seed
71+
72+
if (hasOrigSeed) {
73+
.GlobalEnv$.Random.seed <- origSeed
74+
} else {
75+
rm(.Random.seed, envir = .GlobalEnv, inherits = FALSE)
8276
}
83-
)
84-
}
77+
# Need to call this to make sure that the value of .Random.seed gets put
78+
# into R's internal RNG state. (Issue #1763)
79+
httpuv::getRNGState()
80+
})
8581

86-
# a homemade version of set.seed(NULL) for backward compatibility with R 2.15.x
87-
reinitializeSeed <- if (getRversion() >= '3.0.0') {
88-
function() set.seed(NULL)
89-
} else function() {
90-
if (exists('.Random.seed', globalenv()))
91-
rm(list = '.Random.seed', pos = globalenv())
92-
stats::runif(1) # generate any random numbers so R can reinitialize the seed
82+
expr
9383
}
9484

9585
# Version of runif that runs with private seed

tests/testthat/test-utils.R

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,27 @@ test_that("Setting the private seed explicitly results in identical values", {
5252
expect_identical(id7, id8)
5353
})
5454

55+
test_that("Private and 'public' random streams are independent and work the same", {
56+
set.seed(0)
57+
public <- c(runif(1), runif(1), runif(1))
58+
withPrivateSeed(set.seed(0))
59+
private <- c(withPrivateSeed(runif(1)), withPrivateSeed(runif(1)), withPrivateSeed(runif(1)))
60+
expect_identical(public, private)
61+
62+
# Interleaved calls to runif() with private and public streams
63+
set.seed(0)
64+
withPrivateSeed(set.seed(0))
65+
public <- numeric()
66+
private <- numeric()
67+
public[1] <- runif(1)
68+
private[1] <- withPrivateSeed(runif(1))
69+
private[2] <- withPrivateSeed(runif(1))
70+
public[2] <- runif(1)
71+
public[3] <- runif(1)
72+
private[3] <- withPrivateSeed(runif(1))
73+
expect_identical(public, private)
74+
})
75+
5576
test_that("need() works as expected", {
5677

5778
# These are all falsy

0 commit comments

Comments
 (0)