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

Fix ConcurrentModificationException due to FrameMetricsAggregator manipulation #2282

Merged
merged 7 commits into from Oct 10, 2022

Conversation

adinauer
Copy link
Member

@adinauer adinauer commented Oct 10, 2022

androidx.core.app.FrameMetricsAggregator is not thread safe, calling .remove() from a background thread may cause a
ConcurrentModificationException

📜 Description

Ensure calling .remove() is done on the UI thread.

💡 Motivation and Context

SentryTracer uses a Timer and TimerTask to finish open Transactions on a background thread. Finishing a transaction also triggers the calculation of the frame metrics as well as unregistering the activity from metric collection (FrameMetricsAggregator.remove(activity)), FrameMetricsAggregator is not thread safe, causing sporadic ConcurrentModificationExceptions.

💚 How did you test it?

Ensured current tests are not breaking, otherwise it's hard to test a ConcurrentModificationException.

📝 Checklist

  • I reviewed the submitted code
  • I added tests to verify the changes
  • I updated the docs if needed
  • No breaking changes

🔮 Next steps

@adinauer adinauer changed the base branch from main to 6.4.x-hotfix October 10, 2022 08:37
@adinauer adinauer changed the title Merge hotfix back into main from 6.4.4 release Fix ConcurrentModificationException due to FrameMetricsAggregator manipulation Oct 10, 2022
@adinauer adinauer marked this pull request as ready for review October 10, 2022 08:39
@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2022

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 301.41 ms 376.38 ms 74.97 ms
Size 1.74 MiB 2.33 MiB 606.15 KiB

Previous results on branch: 6.4.x

Startup times

Revision Plain With Sentry Diff
5d9a6b1 372.04 ms 429.63 ms 57.59 ms
5a92bc6 352.02 ms 378.21 ms 26.19 ms
efa513a 322.63 ms 362.73 ms 40.10 ms
222b94c 338.71 ms 374.32 ms 35.61 ms
9a87290 318.37 ms 369.65 ms 51.27 ms
f48be76 316.26 ms 363.22 ms 46.96 ms
255f89e 320.54 ms 357.53 ms 36.99 ms

App size

Revision Plain With Sentry Diff
5d9a6b1 1.73 MiB 2.29 MiB 579.50 KiB
5a92bc6 1.74 MiB 2.33 MiB 606.15 KiB
efa513a 1.73 MiB 2.29 MiB 579.50 KiB
222b94c 1.74 MiB 2.33 MiB 606.32 KiB
9a87290 1.73 MiB 2.29 MiB 579.49 KiB
f48be76 1.74 MiB 2.33 MiB 606.32 KiB
255f89e 1.74 MiB 2.33 MiB 606.32 KiB


@Nullable SparseIntArray[] framesRates = null;
try {
framesRates = frameMetricsAggregator.getMetrics();
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not touch OnFrameMetricsAvailableListener.
We don't need a try/catch, if so, what is the concern?

Copy link
Member

Choose a reason for hiding this comment

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

I agree, I still added it in order to make all frameMetricsAggregator.x calls safer. Yes, it's safe now but in theory it could change with a future update to the androidx lib. But I'm fine with removing it 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Your call, upgrades to the androidx lib can cause new issues, I agree.

@codecov-commenter
Copy link

codecov-commenter commented Oct 10, 2022

Codecov Report

Base: 80.62% // Head: 80.62% // No change to project coverage 👍

Coverage data is based on head (497457f) compared to base (e63148d).
Patch has no changes to coverable lines.

Additional details and impacted files
@@               Coverage Diff               @@
##             6.4.x-hotfix    #2282   +/-   ##
===============================================
  Coverage           80.62%   80.62%           
  Complexity           3368     3368           
===============================================
  Files                 240      240           
  Lines               12388    12388           
  Branches             1646     1646           
===============================================
  Hits                 9988     9988           
  Misses               1791     1791           
  Partials              609      609           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@markushi
Copy link
Member

markushi commented Oct 10, 2022

Seeing this finally getting ready for merge, the changes are functional but definitely not very readable/maintainable.
I was wondering if we should introduce a helper function do deal with the try/catch and ensure being run on the main thread.

E.g.


void runSafelyOnMainThread(Runnable runnable, String tag) {
    try {
        if (MainThreadChecker.isMainThread()) {
            runnable.run();
        } else {
            handler.post(
                () -> {
                try {
                    runnable.run();
                } catch (Throwable t) {
                    if (logger != null) {
                    logger.log(SentryLevel.ERROR, "Failed to run " + tag, t);
                    }
                }
                });
        }
    } catch (Throwable t) {
        if (logger != null) {
            logger.log(SentryLevel.ERROR, "Failed to run" + tag, t);
        }
    }
}

...
runSafelyOnMainThread(() -> { frameMetricsAggregator.stop(); }, "frameMetricsAggregator.stop");

Copy link
Member Author

@adinauer adinauer left a comment

Choose a reason for hiding this comment

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

Feel free to merge. If we can extract some of the duplicated code to make it easier to read / maintain I'd vote for that.

CHANGELOG.md Show resolved Hide resolved
@adinauer
Copy link
Member Author

Can't approve since I created the PR

Co-authored-by: Alexander Dinauer <adinauer@users.noreply.github.com>
@philipphofmann
Copy link
Member

@adinauer, is there a good reason why this PR doesn't have a description? Can we please add one?

Copy link
Member

@philipphofmann philipphofmann left a comment

Choose a reason for hiding this comment

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

LGTM

@markushi
Copy link
Member

I've updated the description (copied it over from the initial PR)

@marandaneto
Copy link
Contributor

Seeing this finally getting ready for merge, the changes are functional but definitely not very readable/maintainable. I was wondering if we should introduce a helper function do deal with the try/catch and ensure being run on the main thread.

E.g.


void runSafelyOnMainThread(Runnable runnable, String tag) {
    try {
        if (MainThreadChecker.isMainThread()) {
            runnable.run();
        } else {
            handler.post(
                () -> {
                try {
                    runnable.run();
                } catch (Throwable t) {
                    if (logger != null) {
                    logger.log(SentryLevel.ERROR, "Failed to run " + tag, t);
                    }
                }
                });
        }
    } catch (Throwable t) {
        if (logger != null) {
            logger.log(SentryLevel.ERROR, "Failed to run" + tag, t);
        }
    }
}

...
runSafelyOnMainThread(() -> { frameMetricsAggregator.stop(); }, "frameMetricsAggregator.stop");

Makes sense, and I'd reuse where we already do something similar.

@adinauer
Copy link
Member Author

Makes sense, and I'd reuse where we already do something similar.

Can we do that as a separate issue / PR as not to block this from being released?

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.

None yet

5 participants