Skip to content

Hubs/Scopes Merge 28 - Fix breadcrumb ordering - #3355

Merged
adinauer merged 34 commits into
8.x.xfrom
feat/hsm-28-breadcrumb-ordering
Apr 23, 2024
Merged

adinauer merged 34 commits into
8.x.xfrom
feat/hsm-28-breadcrumb-ordering

Conversation

@adinauer

@adinauer adinauer commented Apr 15, 2024 •

Copy link
Copy Markdown
Member

#skip-changelog

📜 Description

Use System.nanoTime() to have a more detailed timestamp for ordering breadcrumbs.
The actual value shouldn't matter too much as breadcrumbs should be created / deserialized in order and nanos are only used if timestamp is the same.

💡 Motivation and Context

To have better ordering of breadcrumbs and avoid random order when timestamp is equal. This also caused flaky tests.

💚 How did you test it?

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

adinauer and others added 30 commits March 27, 2024 16:30
@github-actions

github-actions Bot commented Apr 15, 2024 •

Copy link
Copy Markdown
Contributor
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against fb074c8

@adinauer adinauer mentioned this pull request Apr 16, 2024
7 tasks
@markushi markushi changed the title Hubs/Scpes Merge 28 - Fix breadcrumb ordering Hubs/Scopes Merge 28 - Fix breadcrumb ordering Apr 19, 2024
Base automatically changed from feat/hsm-27-discussions to 8.x.x April 22, 2024 14:24
public int compareTo(@NotNull Breadcrumb o) {
// TODO also use nano time if equal
return timestamp.compareTo(o.timestamp);
int timestampCompare = timestamp.compareTo(o.timestamp);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In order to have consistent ordering, should we always use nanos?
We would also have to change the constructor to take a nano time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah makes sense, e.g. for OTEL where we likely have to create breadcrumbs after the fact. Will change it to only use nanos in a follow up PR.

@adinauer
adinauer merged commit 2d01626 into 8.x.x Apr 23, 2024
@adinauer
adinauer deleted the feat/hsm-28-breadcrumb-ordering branch April 23, 2024 13:14
runningcode added a commit that referenced this pull request Sep 14, 2026
Breadcrumb.compareTo ordered purely by a System.nanoTime() reading taken
in the constructor. A breadcrumb rebuilt from a serialized one — read
back from disk, or handed over by a hybrid SDK — got that reading at
parse time, so a breadcrumb recorded yesterday sorted as if it had just
happened, and the merged order in CombinedScopeView became parse order.
The clone constructor had the same problem: copying a breadcrumb moved
it to the end of the order.

Order by the recorded timestamp instead, and keep the creation tick only
as the tie-breaker it was added for in #3355, since timestamps are
millisecond-granular. A deserialized breadcrumb carries no tick, and a
clone carries the original's, so neither jumps position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Sep 15, 2026
Breadcrumb.compareTo ordered purely by a System.nanoTime() reading taken
in the constructor. A breadcrumb rebuilt from a serialized one — read
back from disk, or handed over by a hybrid SDK — got that reading at
parse time, so a breadcrumb recorded yesterday sorted as if it had just
happened, and the merged order in CombinedScopeView became parse order.
The clone constructor had the same problem: copying a breadcrumb moved
it to the end of the order.

Order by the recorded timestamp instead, and keep the creation tick only
as the tie-breaker it was added for in #3355, since timestamps are
millisecond-granular. A deserialized breadcrumb carries no tick, and a
clone carries the original's, so neither jumps position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Sep 16, 2026
Breadcrumb.compareTo ordered purely by a System.nanoTime() reading taken
in the constructor. A breadcrumb rebuilt from a serialized one — read
back from disk, or handed over by a hybrid SDK — got that reading at
parse time, so a breadcrumb recorded yesterday sorted as if it had just
happened, and the merged order in CombinedScopeView became parse order.
The clone constructor had the same problem: copying a breadcrumb moved
it to the end of the order.

Order by the recorded timestamp instead, and keep the creation tick only
as the tie-breaker it was added for in #3355, since timestamps are
millisecond-granular. A deserialized breadcrumb carries no tick, and a
clone carries the original's, so neither jumps position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Sep 16, 2026
* fix(core): Order breadcrumbs by their own timestamp (JAVA-579)

Breadcrumb.compareTo ordered purely by a System.nanoTime() reading taken
in the constructor. A breadcrumb rebuilt from a serialized one — read
back from disk, or handed over by a hybrid SDK — got that reading at
parse time, so a breadcrumb recorded yesterday sorted as if it had just
happened, and the merged order in CombinedScopeView became parse order.
The clone constructor had the same problem: copying a breadcrumb moved
it to the end of the order.

Order by the recorded timestamp instead, and keep the creation tick only
as the tie-breaker it was added for in #3355, since timestamps are
millisecond-granular. A deserialized breadcrumb carries no tick, and a
clone carries the original's, so neither jumps position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* changelog

* ref(core): Keep the creation tick on deserialized breadcrumbs

Always fill the tick and let the timestamp comparison carry the fix, so
ordering no longer depends on every caller using a stable sort.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ref(core): Sort breadcrumbs by their natural ordering in SentryClient

SortBreadcrumbsByDate compared timestamps only, so ties fell through to
the sort's stability. Breadcrumb.compareTo now defines that same order
with a defined tie-breaker, leaving the comparator a weaker duplicate of
it and the codebase with two definitions of breadcrumb order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
runningcode added a commit that referenced this pull request Sep 18, 2026
* ref(android): Mark AppStartMetrics.setAppStartType as @testonly (#6121)

No production code calls this setter; the cold/warm classification assigns
the field directly. Its only callers are tests, so annotate it the way the
neighboring test seams (setFirstIdle, getFirstIdle, clear) already are.

It has to stay public: most of those call sites live in
io.sentry.android.core, a different package from AppStartMetrics.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(android): Recover MemoryLimiter app exits on startup (JAVA-687) (#6111)

Introduce a new MemoryLimiterIntegration that captures process deaths attributable to Android 17's new [MemoryLimiter](https://source.android.com/docs/core/perf/memory-limiter#process-monitoring) system service (see also [here](https://android-developers.googleblog.com/2026/06/prioritizing-memory-efficiency-steps-for-android-17.html)).

Process death info is extracted from ApplicationExitInfo on the next app launch. We then enrich it with persisted SDK state and send it to Relay as a fatal Sentry event.

Integration is experimental; is only available for Android API >= 37; and is disabled by default.

* fix(core): Keep resolving the hostname after Sentry.close() (#6119)

* fix(core): Keep resolving the hostname after Sentry.close()

MainEventProcessor was Closeable, so Scopes.close() closed it, and it shut
down the process-wide HostnameCache singleton. Nothing ever replaced that
singleton: INSTANCE is assigned once and never cleared, so a re-init handed the
same shut-down cache to the new MainEventProcessor, and to MetricsApi and
LoggerApi, which read it directly.

The damage was silent and permanent. While the cache was still fresh,
getHostname() kept returning the value it already had. On the first expiry
after the close, getHostname() flipped updateRunning to true and then
submit() threw RejectedExecutionException on the terminated executor. That is
a RuntimeException, so it was swallowed into handleCacheUpdateFailure(), but
the updateRunning reset lives in the submitted callable's finally block, which
never ran. updateRunning stayed true, so the compareAndSet guard failed from
then on and no refresh was ever attempted again. server_name froze at its last
resolved value for the life of the process, with no exception and no log line.

Nothing needs to close this cache. Its executor is a single daemon thread with
allowCoreThreadTimeOut(true) and a 30 second keep-alive, so the worker exits on
its own once idle and never holds up process exit; the thread exists for about
30 seconds out of every 5 hour refresh interval. Scopes.close() already leaves
the timer executor running for exactly this reason.

The one test that covered this path, SentryClientTest's `when client is closed,
hostname cache is closed`, asserted isClosed() on a processor that had never
resolved a hostname, where isClosed() returned true because the cache was still
null. It never exercised the behavior it named. Replaced with an assertion that
MainEventProcessor is not Closeable, which fails if the wiring comes back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* changelog

* fix(core): Clear updateRunning when a refresh cannot be queued

updateRunning is cleared in exactly one place, the submitted callable's finally
block, so it is cleared if and only if the callable runs. Every failure from
Future.get() leaves the callable running, so it still clears the flag itself.
A failure from submit() does not: the callable was never queued, nothing clears
the flag, and the compareAndSet guard in getHostname() then fails forever, so
no refresh is ever attempted again.

Removing MainEventProcessor's close() took away the only reachable way to make
submit() throw, but the invariant was still wrong: a bounded queue, a shutdown
added later, or a failure to start a thread would silently resurrect the same
permanent freeze.

Splitting submit() out of the try means the two cases can be told apart.
Clearing the flag on a timeout or an interrupt as well would be wrong, since
the callable is still running there and refreshes would pile up behind a slow
lookup; MainEventProcessorTest's `sets servername to null if retrieving takes
longer time` covers that path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(core): Drop the not-Closeable assertion

It asserted a type relationship rather than behavior, which says nothing about
whether the hostname keeps resolving. The behavior that matters is covered by
HostnameCacheTest: `worker thread times out while idle instead of staying
alive` guards the self-terminating executor that makes closing unnecessary, and
`a refresh that cannot be queued does not stop later refreshes` guards the
latch that turned a one-off failure into a permanent one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(core): Order breadcrumbs by their own timestamp (JAVA-579) (#6097)

* fix(core): Order breadcrumbs by their own timestamp (JAVA-579)

Breadcrumb.compareTo ordered purely by a System.nanoTime() reading taken
in the constructor. A breadcrumb rebuilt from a serialized one — read
back from disk, or handed over by a hybrid SDK — got that reading at
parse time, so a breadcrumb recorded yesterday sorted as if it had just
happened, and the merged order in CombinedScopeView became parse order.
The clone constructor had the same problem: copying a breadcrumb moved
it to the end of the order.

Order by the recorded timestamp instead, and keep the creation tick only
as the tie-breaker it was added for in #3355, since timestamps are
millisecond-granular. A deserialized breadcrumb carries no tick, and a
clone carries the original's, so neither jumps position.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* changelog

* ref(core): Keep the creation tick on deserialized breadcrumbs

Always fill the tick and let the timestamp comparison carry the fix, so
ordering no longer depends on every caller using a stable sort.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ref(core): Sort breadcrumbs by their natural ordering in SentryClient

SortBreadcrumbsByDate compared timestamps only, so ties fell through to
the sort's stability. Breadcrumb.compareTo now defines that same order
with a defined tie-breaker, leaving the comparator a weaker duplicate of
it and the codebase with two definitions of breadcrumb order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(android): Mark MemoryLimiter sessions as having exited abnormally (JAVA-687) (#6113)

Mark recovered MemoryLimiter exits as abnormal session endings so release health no longer treats the terminated process as a healthy exit. Reuses the existing AbnormalExit cache repair path with a stable memory_limiter mechanism and the OS exit timestamp.

* fix(changelog): Relocate misplaced CHANGELOG entries ahead of release (#6123)

* release: 8.57.0

* fix(core): Disable manifest URL caching when reading versions (JAVA-730) (#6124)

ManifestVersionReader was retaining jar-backed inflater state while scanning META-INF/MANIFEST.MF entries. Disable URL caching for those reads and close the stream after parsing.

* chore(deps): bump the github-actions group across 1 directory with 4 updates (#6110)

Bumps the github-actions group with 4 updates in the / directory: [actions/setup-java](https://github.com/actions/setup-java), [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/analyze](https://github.com/github/codeql-action) and [getsentry/craft](https://github.com/getsentry/craft).


Updates `actions/setup-java` from 6.0.0 to 6.0.1
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](actions/setup-java@dd06d9c...de7274f)

Updates `github/codeql-action/init` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...b96794f)

Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...b96794f)

Updates `getsentry/craft` from 2.30.1 to 2.31.0
- [Release notes](https://github.com/getsentry/craft/releases)
- [Changelog](https://github.com/getsentry/craft/blob/master/CHANGELOG.md)
- [Commits](getsentry/craft@cd1e829...55694f8)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: getsentry/craft
  dependency-version: 2.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Adam Brown <adam.brown@sentry.io>
Co-authored-by: 0xadam-brown <281682121+0xadam-brown@users.noreply.github.com>
Co-authored-by: sentry-release-bot[bot] <180476844+sentry-release-bot[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

3 participants