@@ -106,6 +106,7 @@ public boolean storeEnvelope(final @NotNull SentryEnvelope envelope, final @NotN
106106 return storeInternal (envelope , hint );
107107 }
108108
109+ @ SuppressWarnings ("JavaUtilDate" )
109110 private boolean storeInternal (final @ NotNull SentryEnvelope envelope , final @ NotNull Hint hint ) {
110111 Objects .requireNonNull (envelope , "Envelope is required." );
111112
@@ -118,8 +119,22 @@ private boolean storeInternal(final @NotNull SentryEnvelope envelope, final @Not
118119 final File previousSessionFile = getPreviousSessionFile (directoryPath );
119120
120121 if (HintUtils .hasType (hint , SessionEnd .class )) {
121- if (!currentSessionFile .delete ()) {
122- options .getLogger ().log (WARNING , "Current envelope doesn't exist." );
122+ try (final @ NotNull ISentryLifecycleToken ignored = sessionLock .acquire ()) {
123+ final @ Nullable Session endingSession = readSessionFromEnvelope (envelope );
124+ final @ Nullable Session currentSession = readSessionFromDisk (currentSessionFile );
125+ final boolean preservePendingSession =
126+ endingSession != null
127+ && currentSession != null
128+ && currentSession .isPendingUnhandled ()
129+ && endingSession .getSessionId () != null
130+ && currentSession .getSessionId () != null
131+ && !Objects .equals (endingSession .getSessionId (), currentSession .getSessionId ())
132+ && endingSession .getStarted () != null
133+ && currentSession .getStarted () != null
134+ && currentSession .getStarted ().after (endingSession .getStarted ());
135+ if (!preservePendingSession && !currentSessionFile .delete ()) {
136+ options .getLogger ().log (WARNING , "Current envelope doesn't exist." );
137+ }
123138 }
124139 }
125140
@@ -129,8 +144,22 @@ private boolean storeInternal(final @NotNull SentryEnvelope envelope, final @Not
129144 }
130145
131146 if (HintUtils .hasType (hint , SessionStart .class )) {
132- movePreviousSession (currentSessionFile , previousSessionFile );
133- updateCurrentSession (currentSessionFile , envelope );
147+ try (final @ NotNull ISentryLifecycleToken ignored = sessionLock .acquire ()) {
148+ final @ Nullable Session startingSession = readSessionFromEnvelope (envelope );
149+ if (startingSession != null ) {
150+ final @ Nullable Session currentSession = readSessionFromDisk (currentSessionFile );
151+ if (currentSession != null && hasSameSessionId (currentSession , startingSession )) {
152+ if (!isNewerPendingOrErrorSnapshot (currentSession , startingSession )) {
153+ writeSessionToDisk (currentSessionFile , startingSession );
154+ }
155+ } else {
156+ movePreviousSession (currentSessionFile , previousSessionFile );
157+ writeSessionToDisk (currentSessionFile , startingSession );
158+ }
159+ } else {
160+ movePreviousSession (currentSessionFile , previousSessionFile );
161+ }
162+ }
134163
135164 boolean crashedLastRun = false ;
136165 final File crashMarkerFile = new File (options .getCacheDirPath (), NATIVE_CRASH_MARKER_FILE );
@@ -274,8 +303,7 @@ private void writeCrashMarkerFile() {
274303 }
275304 }
276305
277- private void updateCurrentSession (
278- final @ NotNull File currentSessionFile , final @ NotNull SentryEnvelope envelope ) {
306+ private @ Nullable Session readSessionFromEnvelope (final @ NotNull SentryEnvelope envelope ) {
279307 final Iterable <SentryEnvelopeItem > items = envelope .getItems ();
280308
281309 // we know that an envelope with a SessionStart hint has a single item inside
@@ -295,7 +323,7 @@ private void updateCurrentSession(
295323 "Item of type %s returned null by the parser." ,
296324 item .getHeader ().getType ());
297325 } else {
298- writeSessionToDisk ( currentSessionFile , session ) ;
326+ return session ;
299327 }
300328 } catch (Throwable e ) {
301329 options .getLogger ().log (ERROR , "Item failed to process." , e );
@@ -309,10 +337,35 @@ private void updateCurrentSession(
309337 item .getHeader ().getType ());
310338 }
311339 } else {
312- options
313- .getLogger ()
314- .log (INFO , "Current envelope %s is empty" , currentSessionFile .getAbsolutePath ());
340+ options .getLogger ().log (INFO , "Current envelope is empty." );
315341 }
342+ return null ;
343+ }
344+
345+ private @ Nullable Session readSessionFromDisk (final @ NotNull File sessionFile ) {
346+ if (!sessionFile .exists ()) {
347+ return null ;
348+ }
349+ try (final Reader reader =
350+ new BufferedReader (new InputStreamReader (new FileInputStream (sessionFile ), UTF_8 ))) {
351+ return serializer .getValue ().deserialize (reader , Session .class );
352+ } catch (Exception e ) {
353+ options .getLogger ().log (ERROR , "Failed to read session from disk." , e );
354+ return null ;
355+ }
356+ }
357+
358+ private boolean isNewerPendingOrErrorSnapshot (
359+ final @ NotNull Session currentSession , final @ NotNull Session startingSession ) {
360+ return (currentSession .isPendingUnhandled () && !startingSession .isPendingUnhandled ())
361+ || currentSession .errorCount () > startingSession .errorCount ();
362+ }
363+
364+ private boolean hasSameSessionId (
365+ final @ NotNull Session firstSession , final @ NotNull Session secondSession ) {
366+ return firstSession .getSessionId () != null
367+ && secondSession .getSessionId () != null
368+ && Objects .equals (firstSession .getSessionId (), secondSession .getSessionId ());
316369 }
317370
318371 private boolean writeEnvelopeToDisk (
@@ -352,6 +405,13 @@ private void writeSessionToDisk(final @NotNull File file, final @NotNull Session
352405 }
353406 }
354407
408+ @ ApiStatus .Internal
409+ public void persistCurrentSession (final @ NotNull Session session ) {
410+ try (final @ NotNull ISentryLifecycleToken ignored = sessionLock .acquire ()) {
411+ writeSessionToDisk (getCurrentSessionFile (directory .getOrCreate ().getAbsolutePath ()), session );
412+ }
413+ }
414+
355415 @ Override
356416 public void discard (final @ NotNull SentryEnvelope envelope ) {
357417 Objects .requireNonNull (envelope , "Envelope is required." );
0 commit comments