posit-dev / posit-dev/positron
Ark: Crash with profvis
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4.3k
- Forks
- 184
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 206
Description
@hadley reports a sporadic (but easy to reproduce) crash with the dev version of profvis in Positron:
devtools::load_all() # in profvis
profvis(f(), simplify = TRUE, rerun = "pause", interval = 0.005)
The relevant part of the backtrace reported in logs looks like:
[R] >>> Backtrace for signal 11
[R] >>> In thread main
[R] 0: std::backtrace::Backtrace::create
[R] 1: ark::traps::backtrace_handler
[R] 2: __simple_esappend
[R] 3: findProfContext
[R] at /Volumes/Builds/R4/R-4.4.0/src/main/eval.c:375:23
[R] 4: doprof
[R] at /Volumes/Builds/R4/R-4.4.0/src/main/eval.c:496:10
[R] 5: __simple_esappend
[R] 6: R_ToplevelExec
[R] at /Volumes/Builds/R4/R-4.4.0/src/main/context.c:807:5
[R] 7: harp::exec::r_check_stack
[R] 8: _r_polled_events
[R] 9: R_ProcessEvents
[R] at /Volumes/Builds/R4/R-4.4.0/src/unix/sys-unix.c:1178:5
[R] 10: R_CheckUserInterrupt
[R] at /Volumes/Builds/R4/R-4.4.0/src/main/errors.c:153:5
[R] 11: profvis_pause
[R] at ./src/pause.c:34:5
The crash happens in findProfContext which we added to R some time ago to power simplified backtraces in profiles.
The crashing sequence is:
- profvis calls
pause()during the backtrace calibration profile (i.e. the main profile has finished) pause()checks for user interrupts.- Ark checks for sufficient stack space within a
R_ToplevelExec()context at interrupt time - The posix signal handler for profiling kicks in right then.
- The
findProfContext()routine called by the signal handler crashes.
Here is where the crash happens:
cptr is unexpectedly NULL. Worth noting that in a top-level-exec context:
R_GlobalContext = R_ToplevelContext = &thiscontext;
findProfContext() is called from doprof() within this loop:
for (RCNTXT *cptr = R_GlobalContext;
cptr != NULL;
cptr = findProfContext(cptr)) {
And the loop in findProfContext() looks for the top-level context:
if (cptr == R_ToplevelContext)
return NULL;
...
while (cptr->nextcontext != R_ToplevelContext)
cptr = cptr->nextcontext;
AFAICT, cptr can only become NULL here if the top-level context is not in the linked list. And indeed in the debug session, I see that R_GlobalContext (our starting point) is the parent of the top-level exec context, which is surprising and it also explains why findProfContext() is not able to find the top-level context. We started too high in the chain.
Now why aren't the global and top-level contexts equal, as would be expected at this time? One explanation would be that something is jumping out of our top-level context (that we created to check for the current stack size). A jump sets the global context to an earlier frame: https://github.com/r-devel/r-svn/blob/08656ceb6a8c0b6fd31f436a16cea03fb614327a/src/main/context.c#L231. But it doesn't change the top-level context which is only reset when we leave it. So during that timeframe it's possible for the global context to be a younger frame than the top-level context.
What could cause a jump out of our top-level context?
-
Is the error thrown in the profile expression relevant? I don't think so because when R crashes we are already out of the main profiling loop, and running the "backtrace calibration" profile (that's the only place
pause()is called). I think the error just helps to speed up the request and get out of the main profiling loop. -
Is there somehow a stack overflow error? It doesn't seem like it as a breakpoint on
R_SignalCStackOverflowis not triggered.
So I'm not sure yet what is the cause of the crash:
- There is a bug in R (perhaps we should just check for
NULLcontext in thatfindProfContext()loop) - Accessing the R API at interrupt time is unsafe
- Accessing the R API from a posix signal handler is unsafe
- A combination of these
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the crash with the profvis commands in the issue, then inspect src/pause.c and the R eval.c paths for findProfContext() and doprof(). Trace the context state during R_ToplevelExec() and the profiling signal; done means identifying the cause of the invalid context and defining a verified resolution.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- r
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100