Skip to content
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

Stacktraces for Swift Concurrency #1919

Closed
Tracked by #3041
kabiroberai opened this issue Jun 24, 2022 · 13 comments · Fixed by #3051
Closed
Tracked by #3041

Stacktraces for Swift Concurrency #1919

kabiroberai opened this issue Jun 24, 2022 · 13 comments · Fixed by #3051

Comments

@kabiroberai
Copy link
Contributor

Platform

iOS

Installed

Swift Package Manager

Version

7.16.0

Steps to Reproduce

  1. Create an async function, with a suspension point (such as await Task.sleep(…))
  2. Capture a Sentry event after the suspension point

It should be possible to repro this with the sample introduced in #1504

Expected Result

The Sentry dashboard shows the entire stack trace, including the async function’s caller

Actual Result

The stack trace stops at the function inside which the error was logged, showing the caller as swift::runJobInEstablishedExecutorContext

Sentry would ideally stitch together parent Task stack frames, similar to how stitchAsyncCode from #998 works for libdispatch.

@kabiroberai
Copy link
Contributor Author

What the stack trace currently looks like: stack trace

@brustolin
Copy link
Contributor

Hey @kabiroberai Thanks for your input. It would be awesome to have the stacktrace stitch, right now we don't have an ETA for this.

@philipphofmann
Copy link
Member

As pointed out by @brustolin, we have an experimental feature for this @kabiroberai. You can enable stitchAsyncCode and let us know what you think.

/**
* Attention: This is an experimental feature. Turning this feature on can have an impact on
* the grouping of your issues.
*
* When enabled, the SDK stitches stack traces of asynchronous code together.
*
* This feature is disabled by default.
*/
@property (nonatomic, assign) BOOL stitchAsyncCode;

@philipphofmann
Copy link
Member

I created an issue for pointing that out in the docs and the wizard getsentry/sentry-docs#5215.

@philipphofmann philipphofmann changed the title Swift Concurrency stack traces are incomplete Make stitchAsyncCode GA Jun 27, 2022
@kabiroberai
Copy link
Contributor Author

If I understand correctly, stitchAsyncCode is specifically for GCD; it doesn't work with Swift Concurrency task stack frames, which require special handling. To see what I mean, check out how Swift's reflection libs handle it at https://github.com/apple/swift/blob/d52dddc7c8448fc9529463bdeb32fbf6b221b5a6/include/swift/Reflection/ReflectionContext.h#L1687-L1699 (the pointer to the current task is stored in thread-local variable 103 on Darwin; there's a ton of other info in there that may be useful for Sentry too!)

@kabiroberai
Copy link
Contributor Author

Also worth mentioning Darwin's backtrace_async which is like backtrace(3) but with async stack frames included. Here's the source in Apple's libc: https://github.com/apple-oss-distributions/Libc/blob/dbde50970329ace8793fca2b63d708d280c33d88/gen/backtrace.c#L47

@philipphofmann philipphofmann changed the title Make stitchAsyncCode GA Stacktraces for Swift Concurrency Jun 28, 2022
@philipphofmann
Copy link
Member

Thanks for pointing that out, @kabiroberai. Yes, the solution should be similar to stitchAsyncCode for GCD.

@github-actions
Copy link

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@github-actions
Copy link

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

@sindresorhus
Copy link

It would be great if this could be prioritized. I have adopted Swift Concurrency in all my apps and now most stack traces in Sentry are unusable.

@brustolin
Copy link
Contributor

With Swift concurrency being more adopted each year I believe we should increase the priority for this.
What do you think @philipphofmann?

@philipphofmann
Copy link
Member

Thanks, @sindresorhus, for pinging us. Yes, we can bump the priority. We have discussions to maybe cover this in our next quarter, which lasts from November to January. I can't make any promises yet, but I will keep you posted.

@philipphofmann
Copy link
Member

Maybe the backtrace_async in execinfo.h could be useful for this

/*!
 * @function backtrace_async
 * Extracts the function return addresses of the current call stack. While
 * backtrace() will only follow the OS call stack, backtrace_async() will
 * prefer the unwind the Swift concurrency continuation stack if invoked
 * from within an async context. In a non-async context this function is
 * strictly equivalent to backtrace().
 *
 * @param array
 * The array of pointers to fill with the return addresses.
 *
 * @param length
 * The maximum number of pointers to write.
 *
 * @param task_id
 * Can be NULL. If non-NULL, the uint32_t pointed to by `task_id` is set to
 * a non-zero value that for the current process uniquely identifies the async
 * task currently running. If called from a non-async context, the value is
 * set to 0 and `array` contains the same values backtrace() would return.
 *
 * Note that the continuation addresses provided by backtrace_async()
 * have an offset of 1 added to them.  Most symbolication engines will
 * substract 1 from the call stack return addresses in order to symbolicate
 * the call site rather than the return location.  With a Swift async
 * continuation, substracting 1 from its address would result in an address
 * in a different function.  This offset allows the returned addresses to be
 * handled correctly by most existing symbolication engines.
 *
 * @result
 * The number of pointers actually written.
 */
API_AVAILABLE(macosx(12.0), ios(15.0), tvos(15.0), watchos(8.0))
size_t backtrace_async(void** array, size_t length, uint32_t *task_id);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment