Skip to content

Conversation

DavisVaughan
Copy link
Member

@DavisVaughan DavisVaughan commented Oct 3, 2025

Branched from #194

This goes one step beyond #194 and puts us in full control of manual generation of the lifecycle_stage condition object.

The point is to lazily generate the conditionMessage() as needed. Unfortunately this does not currently save us any time because base R's signalCondition() eagerly calls conditionMessage() too soon (see inline comments). But switching to this is probably still worth it since I'd like to see if Luke would fix this in base R.

cross::bench_branches(\() {
  library(lifecycle)
  
  # trigger the 8 hour warning once
  deprecate_soft("1.1.0", I("my thing"), details = "for this reason")

  bench::mark(
    deprecate_soft("1.1.0", I("my thing"), details = "for this reason"),
    iterations = 100000
  )
})

With #194 alone

#> # A tibble: 2 × 7
#>   branch                   expression                                 min median `itr/sec` mem_alloc `gc/sec`
#>   <chr>                    <bch:expr>                              <bch:> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 feature/manual-condition "deprecate_soft(\"1.1.0\", I(\"my thin… 48.5µs 51.1µs    18777.    8.56KB     160.
#> 2 main                     "deprecate_soft(\"1.1.0\", I(\"my thin… 73.3µs 75.6µs    12899.    8.56KB     169.

This PR with current R (a little faster from switching away from cnd())

#> # A tibble: 2 × 7
#>   branch                 expression                        min median `itr/sec` mem_alloc `gc/sec`
#>   <chr>                  <bch:expr>                     <bch:> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 feature/lazy-condition "deprecate_soft(\"1.1.0\", I(… 44.4µs 46.2µs    20899.    8.56KB     58.1
#> 2 main                   "deprecate_soft(\"1.1.0\", I(… 72.8µs 76.5µs    12668.    8.56KB     51.4

By putting in a dummy message of "x" in conditionMessage.lifecycle_stage() to simulate what lazy messaging should get us:

#> # A tibble: 2 × 7
#>   branch                 expression                        min median `itr/sec` mem_alloc `gc/sec`
#>   <chr>                  <bch:expr>                     <bch:> <bch:>     <dbl> <bch:byt>    <dbl>
#> 1 feature/lazy-condition "deprecate_soft(\"1.1.0\", I(… 34.3µs   36µs    26839.    8.56KB     64.0
#> 2 main                   "deprecate_soft(\"1.1.0\", I(… 72.9µs 76.7µs    12725.    8.56KB     51.6

@@ -1,5 +1,7 @@
# lifecycle (development version)

* The condition generated by `signal_stage()` no longer directly exposes fields such as `cnd$package` and `cnd$function_nm`.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked around on GitHub and can't find any usage of this

Comment on lines +69 to 79
# `cnd_signal()` calls `signalCondition()`, which currently eagerly evaluates
# `conditionMessage()`, meaning that right now we don't save any time by making
# the message generation lazy. But we are hoping to fix this in base R in the
# future, since the message is only used in the error path and could be
# generated on demand at that point.
# https://github.com/wch/r-source/blob/f200c30b1a20dfa9394d7facff616e9cb2a42c6d/src/library/base/R/conditions.R#L157-L163
# https://github.com/wch/r-source/blob/f200c30b1a20dfa9394d7facff616e9cb2a42c6d/src/main/errors.c#L1904-L1909
#' @export
conditionMessage.lifecycle_stage <- function(c) {
lifecycle_stage_cnd_data(c)$message
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is my description of the current issue with signalCondition()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the interim, do you think it's worth some hack to call .Internal(.signalCondition(cond, msg, call)) directly? Might be interesting to do it, just to see what the performance implication is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the last example of #195 (comment) I show using a dummy message of "x" which roughly simulates the performance implications I think.

Looks like the lazy way would be roughly another 20% faster on top of everything else so far? 36µs vs 46.2µs from above.

I'm not sure that's worth the hackery because I'm not sure what we'd put in there for msg. We could put a dummy message there but if something triggers this weird C branch where the msg is actually used then users would see it
https://github.com/wch/r-source/blob/f200c30b1a20dfa9394d7facff616e9cb2a42c6d/src/main/errors.c#L1904-L1909

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to get everything I have so far merged and do a draft PR with the hackery to take a look at it "for real"

Comment on lines +69 to 79
# `cnd_signal()` calls `signalCondition()`, which currently eagerly evaluates
# `conditionMessage()`, meaning that right now we don't save any time by making
# the message generation lazy. But we are hoping to fix this in base R in the
# future, since the message is only used in the error path and could be
# generated on demand at that point.
# https://github.com/wch/r-source/blob/f200c30b1a20dfa9394d7facff616e9cb2a42c6d/src/library/base/R/conditions.R#L157-L163
# https://github.com/wch/r-source/blob/f200c30b1a20dfa9394d7facff616e9cb2a42c6d/src/main/errors.c#L1904-L1909
#' @export
conditionMessage.lifecycle_stage <- function(c) {
lifecycle_stage_cnd_data(c)$message
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the interim, do you think it's worth some hack to call .Internal(.signalCondition(cond, msg, call)) directly? Might be interesting to do it, just to see what the performance implication is.

Base automatically changed from feature/manual-condition to main October 3, 2025 17:14
@DavisVaughan DavisVaughan force-pushed the feature/lazy-condition branch from 0b44299 to 67e50bf Compare October 3, 2025 17:15
@DavisVaughan DavisVaughan merged commit 71a3e12 into main Oct 3, 2025
12 checks passed
@DavisVaughan DavisVaughan deleted the feature/lazy-condition branch October 3, 2025 17:16
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

Successfully merging this pull request may close these issues.

2 participants