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

perf: cache debug images #1965

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/Sentry/Public/SentryDebugImageProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (NSArray<SentryDebugMeta *> *)getDebugImages;

/**
* Clears the cached debug images.
*/
- (void)clearCachedDebugImages;

@end

NS_ASSUME_NONNULL_END
23 changes: 23 additions & 0 deletions Sources/Sentry/SentryDebugImageProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "SentryStacktrace.h"
#import "SentryThread.h"
#import <Foundation/Foundation.h>
#include <mach-o/dyld.h>

@interface
SentryDebugImageProvider ()
Expand All @@ -19,6 +20,8 @@

@implementation SentryDebugImageProvider

static NSArray<SentryDebugMeta *> *_Nullable cachedDebugImages;

- (instancetype)init
{

Expand All @@ -36,9 +39,24 @@ - (instancetype)initWithBinaryImageProvider:(id<SentryCrashBinaryImageProvider>)
if (self = [super init]) {
self.binaryImageProvider = binaryImageProvider;
}

_dyld_register_func_for_add_image(clearCachedDebugImages);
_dyld_register_func_for_remove_image(clearCachedDebugImages);

Comment on lines +43 to +45
Copy link
Member

Choose a reason for hiding this comment

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

In some cases your callback function may be called on a different thread (namely the thread that is loading the library), see here: https://twitter.com/gparker/status/1266572192126386176?s=20&t=jXOtWHAPuZ8oUrlUj788jQ

In this scenario the callback functions are accessing cachedDebugImages in a non-thread safe way, so you would need to use a lock to guard all reads/writes to cachedDebugImages.

return self;
}

void
clearCachedDebugImages(const struct mach_header *header, intptr_t slide)
{
cachedDebugImages = nil;
}

- (void)clearCachedDebugImages
{
cachedDebugImages = nil;
}

- (NSArray<SentryDebugMeta *> *)getDebugImagesForThreads:(NSArray<SentryThread *> *)threads
{
NSMutableSet<NSString *> *imageAdresses = [[NSMutableSet alloc] init];
Expand Down Expand Up @@ -66,6 +84,10 @@ - (instancetype)initWithBinaryImageProvider:(id<SentryCrashBinaryImageProvider>)

- (NSArray<SentryDebugMeta *> *)getDebugImages
{
if (cachedDebugImages != nil) {
return cachedDebugImages;
}

NSMutableArray<SentryDebugMeta *> *debugMetaArray = [NSMutableArray new];

NSInteger imageCount = [self.binaryImageProvider getImageCount];
Expand All @@ -75,6 +97,7 @@ - (instancetype)initWithBinaryImageProvider:(id<SentryCrashBinaryImageProvider>)
[debugMetaArray addObject:debugMeta];
}

cachedDebugImages = debugMetaArray;
return debugMetaArray;
}

Expand Down
26 changes: 24 additions & 2 deletions Tests/SentryTests/SentryCrash/SentryDebugImageProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import XCTest
class SentryDebugImageProviderTests: XCTestCase {

private class Fixture {
let imageProvider = TestSentryCrashBinaryImageProvider()

func getSut(images: [SentryCrashBinaryImage] = []) -> SentryDebugImageProvider {
let imageProvider = TestSentryCrashBinaryImageProvider()
imageProvider.imageCount = images.count
imageProvider.binaryImage = images
return SentryDebugImageProvider(binaryImageProvider: imageProvider)
Expand Down Expand Up @@ -72,6 +73,27 @@ class SentryDebugImageProviderTests: XCTestCase {
XCTAssertEqual("apple", debugMeta.type)
XCTAssertEqual(352_256, debugMeta.imageSize)
}

func test_DebugImageCache() {
let twoImages = Array(fixture.getTestImages().dropLast())
let sut = fixture.getSut(images: twoImages)

var actual = sut.getDebugImages()
XCTAssertEqual(2, actual.count)

// We update the debug images under the hood
fixture.imageProvider.binaryImage = fixture.getTestImages()
fixture.imageProvider.imageCount = fixture.imageProvider.binaryImage.count

// It's still using the cached value
actual = sut.getDebugImages()
XCTAssertEqual(2, actual.count)

// Clear the cache and get the new value
sut.clearCachedDebugImages()
actual = sut.getDebugImages()
XCTAssertEqual(3, actual.count)
}

func testImageVmAddressIsZero() {
let image = SentryDebugImageProviderTests.createSentryCrashBinaryImage(vmAddress: 0)
Expand Down Expand Up @@ -156,7 +178,7 @@ class SentryDebugImageProviderTests: XCTestCase {

XCTAssertEqual(actual.count, 0)
}

private static func createSentryCrashBinaryImage(
address: UInt64 = 0,
vmAddress: UInt64 = 0,
Expand Down