-
-
Notifications
You must be signed in to change notification settings - Fork 474
fix(core): [Unhandled Sessions 2] Don't let a queued SessionStart overwrite a newer session #5920
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
Changes from all commits
0e52a46
1ae2b46
e62793f
af3fd91
59717b2
55fc569
407c39d
8551441
d198a04
b24a47e
aaa4154
f236516
2c1d462
d9def2e
17f7923
153074c
c120b31
d5fec24
f4dfc80
688a43f
0a2dda4
dbfcd3f
b28cadf
305a48d
d3af8ad
2161da8
2e8a06f
a7f837f
ae99f6c
d52eb4a
8a95759
0a6e2c4
914f836
3445b94
6ca65c5
dfdd999
487daa8
f7575a1
ae2f5da
d0cbd81
4256a7b
9b00da8
76f3fa4
8fe5142
4c5d7a2
8437702
f8fee2b
398f0eb
b81fcdc
3347255
116abce
7f47fa4
ef55c1a
7a5cd31
160a4b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,6 +76,12 @@ public class EnvelopeCache extends CacheStrategy implements IEnvelopeCache { | |
| protected final @NotNull AutoClosableReentrantLock cacheLock = new AutoClosableReentrantLock(); | ||
| protected final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock(); | ||
|
|
||
| /** | ||
| * Session id last written to the current session file by {@link #persistCurrentSession(Session)}, | ||
| * which bypasses the transport queue that every other write to that file goes through. | ||
| */ | ||
| private @Nullable String lastPersistedSessionId; | ||
|
|
||
| public static @NotNull IEnvelopeCache create(final @NotNull SentryOptions options) { | ||
| final String cacheDirPath = options.getCacheDirPath(); | ||
| final int maxCacheItems = options.getMaxCacheItems(); | ||
|
|
@@ -118,8 +124,11 @@ private boolean storeInternal(final @NotNull SentryEnvelope envelope, final @Not | |
| final File previousSessionFile = getPreviousSessionFile(directoryPath); | ||
|
|
||
| if (HintUtils.hasType(hint, SessionEnd.class)) { | ||
| if (!currentSessionFile.delete()) { | ||
| options.getLogger().log(WARNING, "Current envelope doesn't exist."); | ||
| try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { | ||
| lastPersistedSessionId = null; | ||
| if (!currentSessionFile.delete()) { | ||
| options.getLogger().log(WARNING, "Current envelope doesn't exist."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -129,8 +138,15 @@ private boolean storeInternal(final @NotNull SentryEnvelope envelope, final @Not | |
| } | ||
|
|
||
| if (HintUtils.hasType(hint, SessionStart.class)) { | ||
| movePreviousSession(currentSessionFile, previousSessionFile); | ||
| updateCurrentSession(currentSessionFile, envelope); | ||
| try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { | ||
| final @Nullable Session startingSession = readSessionFromEnvelope(envelope); | ||
| if (!isAlreadyPersisted(startingSession)) { | ||
| movePreviousSession(currentSessionFile, previousSessionFile); | ||
| if (startingSession != null) { | ||
| writeSessionToDisk(currentSessionFile, startingSession); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+141
to
+149
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Today The issue is that the SessionStart envelope is queued, so it can land after that: So we skip the move and the write if the SessionStart is for a session we already persisted. Nothing else changes.
cursor[bot] marked this conversation as resolved.
|
||
|
|
||
| boolean crashedLastRun = false; | ||
| final File crashMarkerFile = new File(options.getCacheDirPath(), NATIVE_CRASH_MARKER_FILE); | ||
|
|
@@ -274,8 +290,7 @@ private void writeCrashMarkerFile() { | |
| } | ||
| } | ||
|
|
||
| private void updateCurrentSession( | ||
| final @NotNull File currentSessionFile, final @NotNull SentryEnvelope envelope) { | ||
| private @Nullable Session readSessionFromEnvelope(final @NotNull SentryEnvelope envelope) { | ||
| final Iterable<SentryEnvelopeItem> items = envelope.getItems(); | ||
|
|
||
| // we know that an envelope with a SessionStart hint has a single item inside | ||
|
|
@@ -295,7 +310,7 @@ private void updateCurrentSession( | |
| "Item of type %s returned null by the parser.", | ||
| item.getHeader().getType()); | ||
| } else { | ||
| writeSessionToDisk(currentSessionFile, session); | ||
| return session; | ||
| } | ||
| } catch (Throwable e) { | ||
| options.getLogger().log(ERROR, "Item failed to process.", e); | ||
|
|
@@ -309,10 +324,26 @@ private void updateCurrentSession( | |
| item.getHeader().getType()); | ||
| } | ||
| } else { | ||
| options | ||
| .getLogger() | ||
| .log(INFO, "Current envelope %s is empty", currentSessionFile.getAbsolutePath()); | ||
| options.getLogger().log(INFO, "Current envelope is empty."); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Whether a {@link SessionStart} envelope refers to the session {@link | ||
| * #persistCurrentSession(Session)} already wrote to the current session file. That copy is the | ||
| * live session, so it is at least as advanced as this envelope. Rotating and overwriting it would | ||
| * file a running session as the previous one and roll back any unhandled error it has recorded | ||
| * since. | ||
| * | ||
| * <p>A null session id never matches, so sessions we cannot tell apart are rotated as before. | ||
| */ | ||
| private boolean isAlreadyPersisted(final @Nullable Session startingSession) { | ||
| if (startingSession == null) { | ||
| return false; | ||
| } | ||
| final @Nullable String startingSessionId = startingSession.getSessionId(); | ||
| return startingSessionId != null && startingSessionId.equals(lastPersistedSessionId); | ||
|
buenaflor marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private boolean writeEnvelopeToDisk( | ||
|
|
@@ -337,7 +368,7 @@ private boolean writeEnvelopeToDisk( | |
| return true; | ||
| } | ||
|
|
||
| private void writeSessionToDisk(final @NotNull File file, final @NotNull Session session) { | ||
| private boolean writeSessionToDisk(final @NotNull File file, final @NotNull Session session) { | ||
| try (final OutputStream outputStream = new FileOutputStream(file); | ||
| final Writer writer = new BufferedWriter(new OutputStreamWriter(outputStream, UTF_8))) { | ||
| options | ||
|
|
@@ -349,6 +380,19 @@ private void writeSessionToDisk(final @NotNull File file, final @NotNull Session | |
| options | ||
| .getLogger() | ||
| .log(ERROR, e, "Error writing Session to offline storage: %s", session.getSessionId()); | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| @ApiStatus.Internal | ||
| public void persistCurrentSession(final @NotNull Session session) { | ||
| try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) { | ||
| final boolean written = | ||
| writeSessionToDisk( | ||
| getCurrentSessionFile(directory.getOrCreate().getAbsolutePath()), session); | ||
| // a failed write truncates the file, so there is no good copy left to protect | ||
| lastPersistedSessionId = written ? session.getSessionId() : null; | ||
| } | ||
|
buenaflor marked this conversation as resolved.
|
||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: When a new
SessionStartenvelope rotates the session file,lastPersistedSessionIdis not updated, leading to a potential state inconsistency with stale envelopes.Severity: LOW
Suggested Fix
To ensure state consistency, update the
lastPersistedSessionIdvariable with the new session's ID immediately afterwriteSessionToDiskis called within theSessionStarthandling block.Prompt for AI Agent