Skip to content

Commit e62793f

Browse files
ref: rename pendingUnhandled to nonTerminatingUnhandledError
"Unhandled" alone is ambiguous: a native crash is also an unhandled error, it just terminates the process and so ends the session as crashed rather than unhandled. Name the flag after the property that actually distinguishes the two and match the vocabulary of captureEnvelopeNonTerminating. Also clarify that the setter only restores the flag when rebuilding a session and must not be used to mutate a live one. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 1ae2b46 commit e62793f

5 files changed

Lines changed: 87 additions & 69 deletions

File tree

‎sentry/api/sentry.api‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4286,12 +4286,12 @@ public final class io/sentry/Session : io/sentry/JsonSerializable, io/sentry/Jso
42864286
public fun getTimestamp ()Ljava/util/Date;
42874287
public fun getUnknown ()Ljava/util/Map;
42884288
public fun getUserAgent ()Ljava/lang/String;
4289-
public fun isPendingUnhandled ()Z
4289+
public fun hasNonTerminatingUnhandledError ()Z
42904290
public fun isTerminated ()Z
4291-
public fun markPendingUnhandled ()Z
4291+
public fun recordNonTerminatingUnhandledError ()Z
42924292
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
42934293
public fun setInitAsTrue ()V
4294-
public fun setPendingUnhandled (Z)V
4294+
public fun setNonTerminatingUnhandledError (Z)V
42954295
public fun setUnknown (Ljava/util/Map;)V
42964296
public fun update (Lio/sentry/Session$State;Ljava/lang/String;Z)Z
42974297
public fun update (Lio/sentry/Session$State;Ljava/lang/String;ZLjava/lang/String;)Z
@@ -4312,7 +4312,7 @@ public final class io/sentry/Session$JsonKeys {
43124312
public static final field ERRORS Ljava/lang/String;
43134313
public static final field INIT Ljava/lang/String;
43144314
public static final field IP_ADDRESS Ljava/lang/String;
4315-
public static final field PENDING_UNHANDLED Ljava/lang/String;
4315+
public static final field NON_TERMINATING_UNHANDLED_ERROR Ljava/lang/String;
43164316
public static final field RELEASE Ljava/lang/String;
43174317
public static final field SEQ Ljava/lang/String;
43184318
public static final field SID Ljava/lang/String;

‎sentry/src/main/java/io/sentry/Session.java‎

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,15 @@ public enum State {
6868
private @Nullable String abnormalMechanism;
6969

7070
/**
71-
* Whether the session experienced an unhandled (but non-terminal) exception. Kept locally and
72-
* persisted with the session, but never sent as a status while the session is alive. On end() the
73-
* session is finalized as {@link State#Unhandled} instead of {@link State#Exited} unless a crash
74-
* escalated it to {@link State#Crashed}.
71+
* Whether the session experienced an unhandled error that did <em>not</em> terminate the process,
72+
* e.g. an unhandled Flutter exception. A native crash is also unhandled, but it kills the process
73+
* and therefore ends the session as {@link State#Crashed} instead.
74+
*
75+
* <p>Kept locally and persisted with the session, but never sent as a status while the session is
76+
* alive. On end() the session is finalized as {@link State#Unhandled} instead of {@link
77+
* State#Exited}, unless a crash escalated it to {@link State#Crashed}.
7578
*/
76-
private boolean pendingUnhandled;
79+
private boolean nonTerminatingUnhandledError;
7780

7881
/** The session lock, ops should be atomic */
7982
private final @NotNull AutoClosableReentrantLock sessionLock = new AutoClosableReentrantLock();
@@ -198,32 +201,41 @@ public int errorCount() {
198201
}
199202

200203
/**
201-
* Whether the session has a pending unhandled (non-terminal) exception that hasn't been finalized
202-
* yet.
204+
* Whether the session experienced an unhandled error that did not terminate the process, and so
205+
* finalizes as {@link State#Unhandled} rather than {@link State#Exited}.
203206
*/
204207
@ApiStatus.Internal
205-
public boolean isPendingUnhandled() {
206-
return pendingUnhandled;
208+
public boolean hasNonTerminatingUnhandledError() {
209+
return nonTerminatingUnhandledError;
207210
}
208211

209212
/**
210-
* Marks the session as having experienced an unhandled (non-terminal) exception without ending
211-
* it. On {@link #end()} the session will be finalized as {@link State#Unhandled} unless a crash
212-
* escalated it to {@link State#Crashed} first.
213+
* Restores the flag when rebuilding a session, i.e. from {@link #clone()} or the deserializer.
214+
*
215+
* <p>Not for use on a live session: unlike {@link #recordNonTerminatingUnhandledError()} this
216+
* neither counts the error nor advances the session's sequence, so a session mutated through this
217+
* setter would be sent as an out-of-date update.
213218
*/
214219
@ApiStatus.Internal
215-
public void setPendingUnhandled(final boolean pendingUnhandled) {
216-
this.pendingUnhandled = pendingUnhandled;
220+
public void setNonTerminatingUnhandledError(final boolean nonTerminatingUnhandledError) {
221+
this.nonTerminatingUnhandledError = nonTerminatingUnhandledError;
217222
}
218223

219-
/** Marks an active session as having experienced an unhandled non-terminal exception. */
224+
/**
225+
* Records that an active session experienced an unhandled error which did not terminate the
226+
* process, counting the error and advancing the session's sequence without ending it. On {@link
227+
* #end()} the session is finalized as {@link State#Unhandled} unless a crash escalated it to
228+
* {@link State#Crashed} first.
229+
*
230+
* @return whether the session was updated, i.e. false if it had already reached a terminal state
231+
*/
220232
@ApiStatus.Internal
221-
public boolean markPendingUnhandled() {
233+
public boolean recordNonTerminatingUnhandledError() {
222234
try (final @NotNull ISentryLifecycleToken ignored = sessionLock.acquire()) {
223235
if (status != State.Ok) {
224236
return false;
225237
}
226-
pendingUnhandled = true;
238+
nonTerminatingUnhandledError = true;
227239
errorCount.incrementAndGet();
228240
init = null;
229241
timestamp = DateUtils.getCurrentDateTime();
@@ -255,7 +267,7 @@ public void end(final @Nullable Date timestamp) {
255267
if (status == State.Ok) {
256268
// a session that experienced an unhandled (but non-terminal) exception is finalized as
257269
// Unhandled rather than Exited.
258-
status = pendingUnhandled ? State.Unhandled : State.Exited;
270+
status = nonTerminatingUnhandledError ? State.Unhandled : State.Exited;
259271
}
260272

261273
if (timestamp != null) {
@@ -308,9 +320,9 @@ public boolean update(
308320
boolean sessionHasBeenUpdated = false;
309321
if (status != null) {
310322
this.status = status;
311-
// a real crash takes precedence over a pending unhandled (non-terminal) exception.
323+
// a crash terminates the process, so it takes precedence over a non-terminating one.
312324
if (status == State.Crashed) {
313-
pendingUnhandled = false;
325+
nonTerminatingUnhandledError = false;
314326
}
315327
sessionHasBeenUpdated = true;
316328
}
@@ -384,7 +396,7 @@ private long getSequenceTimestamp(final @NotNull Date timestamp) {
384396
environment,
385397
release,
386398
abnormalMechanism);
387-
session.setPendingUnhandled(pendingUnhandled);
399+
session.setNonTerminatingUnhandledError(nonTerminatingUnhandledError);
388400
return session;
389401
}
390402

@@ -407,7 +419,7 @@ public static final class JsonKeys {
407419
public static final String IP_ADDRESS = "ip_address";
408420
public static final String USER_AGENT = "user_agent";
409421
public static final String ABNORMAL_MECHANISM = "abnormal_mechanism";
410-
public static final String PENDING_UNHANDLED = "pending_unhandled";
422+
public static final String NON_TERMINATING_UNHANDLED_ERROR = "non_terminating_unhandled_error";
411423
}
412424

413425
@Override
@@ -438,8 +450,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
438450
if (abnormalMechanism != null) {
439451
writer.name(JsonKeys.ABNORMAL_MECHANISM).value(logger, abnormalMechanism);
440452
}
441-
if (pendingUnhandled) {
442-
writer.name(JsonKeys.PENDING_UNHANDLED).value(pendingUnhandled);
453+
if (nonTerminatingUnhandledError) {
454+
writer.name(JsonKeys.NON_TERMINATING_UNHANDLED_ERROR).value(nonTerminatingUnhandledError);
443455
}
444456
writer.name(JsonKeys.ATTRS);
445457
writer.beginObject();
@@ -497,7 +509,7 @@ public static final class Deserializer implements JsonDeserializer<Session> {
497509
String environment = null;
498510
String release = null; // @NotNull
499511
String abnormalMechanism = null;
500-
boolean pendingUnhandled = false;
512+
boolean nonTerminatingUnhandledError = false;
501513

502514
Map<String, Object> unknown = null;
503515
while (reader.peek() == JsonToken.NAME) {
@@ -541,9 +553,10 @@ public static final class Deserializer implements JsonDeserializer<Session> {
541553
case JsonKeys.ABNORMAL_MECHANISM:
542554
abnormalMechanism = reader.nextStringOrNull();
543555
break;
544-
case JsonKeys.PENDING_UNHANDLED:
545-
final Boolean pendingUnhandledValue = reader.nextBooleanOrNull();
546-
pendingUnhandled = pendingUnhandledValue != null && pendingUnhandledValue;
556+
case JsonKeys.NON_TERMINATING_UNHANDLED_ERROR:
557+
final Boolean nonTerminatingUnhandledErrorValue = reader.nextBooleanOrNull();
558+
nonTerminatingUnhandledError =
559+
nonTerminatingUnhandledErrorValue != null && nonTerminatingUnhandledErrorValue;
547560
break;
548561
case JsonKeys.ATTRS:
549562
reader.beginObject();
@@ -604,7 +617,7 @@ public static final class Deserializer implements JsonDeserializer<Session> {
604617
environment,
605618
release,
606619
abnormalMechanism);
607-
session.setPendingUnhandled(pendingUnhandled);
620+
session.setNonTerminatingUnhandledError(nonTerminatingUnhandledError);
608621
session.setUnknown(unknown);
609622
reader.endObject();
610623
return session;

‎sentry/src/test/java/io/sentry/PreviousSessionFinalizerTest.kt‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,14 @@ class PreviousSessionFinalizerTest {
201201
}
202202

203203
@Test
204-
fun `if previous session has pending unhandled and no crash marker, finalizes as unhandled`() {
204+
fun `if previous session has a non-terminating unhandled error and no crash marker, finalizes as unhandled`() {
205205
val finalizer =
206206
fixture.getSut(
207207
tmpDir,
208208
session =
209-
Session(null, null, null, "io.sentry.sample@1.0").apply { setPendingUnhandled(true) },
209+
Session(null, null, null, "io.sentry.sample@1.0").apply {
210+
setNonTerminatingUnhandledError(true)
211+
},
210212
)
211213
finalizer.run()
212214

@@ -216,18 +218,20 @@ class PreviousSessionFinalizerTest {
216218
val session = fixture.sessionFromEnvelope(this)
217219
session.release == "io.sentry.sample@1.0" &&
218220
session.status == Session.State.Unhandled &&
219-
session.isPendingUnhandled
221+
session.hasNonTerminatingUnhandledError()
220222
}
221223
)
222224
}
223225

224226
@Test
225-
fun `if previous session has pending unhandled but a native crash marker exists, finalizes as crashed`() {
227+
fun `if previous session has a non-terminating unhandled error but a native crash marker exists, finalizes as crashed`() {
226228
val finalizer =
227229
fixture.getSut(
228230
tmpDir,
229231
session =
230-
Session(null, null, null, "io.sentry.sample@1.0").apply { setPendingUnhandled(true) },
232+
Session(null, null, null, "io.sentry.sample@1.0").apply {
233+
setNonTerminatingUnhandledError(true)
234+
},
231235
nativeCrashTimestamp = DateUtils.getDateTime("2023-10-01T00:00:00.000Z"),
232236
)
233237
finalizer.run()

‎sentry/src/test/java/io/sentry/SessionTest.kt‎

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ class SessionTest {
1111
private fun okSession(): Session = Session(null, null, "environment", "release")
1212

1313
@Test
14-
fun `markPendingUnhandled atomically updates an Ok session`() {
14+
fun `recordNonTerminatingUnhandledError atomically updates an Ok session`() {
1515
val session = okSession()
1616
val initialTimestamp = session.timestamp
1717

18-
val updated = session.markPendingUnhandled()
18+
val updated = session.recordNonTerminatingUnhandledError()
1919

2020
assertThat(updated).isTrue()
2121
assertThat(session.status).isEqualTo(Session.State.Ok)
22-
assertThat(session.isPendingUnhandled).isTrue()
22+
assertThat(session.hasNonTerminatingUnhandledError()).isTrue()
2323
assertThat(session.errorCount()).isEqualTo(1)
2424
assertThat(session.init).isNull()
2525
assertThat(session.timestamp).isNotNull()
@@ -28,17 +28,18 @@ class SessionTest {
2828
}
2929

3030
@Test
31-
fun `markPendingUnhandled does not change terminal sessions`() {
31+
fun `recordNonTerminatingUnhandledError does not change terminal sessions`() {
3232
for (state in Session.State.entries.filter { it != Session.State.Ok }) {
3333
val session = okSession()
3434
session.update(state, null, false)
3535
val before = session.clone()
3636

37-
val updated = session.markPendingUnhandled()
37+
val updated = session.recordNonTerminatingUnhandledError()
3838

3939
assertThat(updated).isFalse()
4040
assertThat(session.status).isEqualTo(before.status)
41-
assertThat(session.isPendingUnhandled).isEqualTo(before.isPendingUnhandled)
41+
assertThat(session.hasNonTerminatingUnhandledError())
42+
.isEqualTo(before.hasNonTerminatingUnhandledError())
4243
assertThat(session.errorCount()).isEqualTo(before.errorCount())
4344
assertThat(session.init).isEqualTo(before.init)
4445
assertThat(session.timestamp).isEqualTo(before.timestamp)
@@ -47,7 +48,7 @@ class SessionTest {
4748
}
4849

4950
@Test
50-
fun `end without pending unhandled finalizes as Exited`() {
51+
fun `end without a non-terminating unhandled error finalizes as Exited`() {
5152
val session = okSession()
5253

5354
session.end()
@@ -56,68 +57,68 @@ class SessionTest {
5657
}
5758

5859
@Test
59-
fun `end with pending unhandled finalizes as Unhandled`() {
60+
fun `end with a non-terminating unhandled error finalizes as Unhandled`() {
6061
val session = okSession()
61-
assertThat(session.isPendingUnhandled).isFalse()
62+
assertThat(session.hasNonTerminatingUnhandledError()).isFalse()
6263

63-
session.setPendingUnhandled(true)
64+
session.setNonTerminatingUnhandledError(true)
6465
session.end()
6566

6667
assertThat(session.status).isEqualTo(Session.State.Unhandled)
67-
assertThat(session.isPendingUnhandled).isTrue()
68+
assertThat(session.hasNonTerminatingUnhandledError()).isTrue()
6869
}
6970

7071
@Test
71-
fun `end with pending unhandled keeps Abnormal as Abnormal`() {
72+
fun `end with a non-terminating unhandled error keeps Abnormal as Abnormal`() {
7273
val session = okSession()
73-
session.setPendingUnhandled(true)
74+
session.setNonTerminatingUnhandledError(true)
7475
session.update(Session.State.Abnormal, null, false, "anr")
7576

7677
session.end()
7778

7879
assertThat(session.status).isEqualTo(Session.State.Abnormal)
79-
assertThat(session.isPendingUnhandled).isTrue()
80+
assertThat(session.hasNonTerminatingUnhandledError()).isTrue()
8081
}
8182

8283
@Test
83-
fun `end with pending unhandled keeps Crashed as Crashed`() {
84+
fun `end with a non-terminating unhandled error keeps Crashed as Crashed`() {
8485
val session = okSession()
85-
session.setPendingUnhandled(true)
86+
session.setNonTerminatingUnhandledError(true)
8687
session.update(Session.State.Crashed, null, false)
8788

8889
session.end()
8990

9091
assertThat(session.status).isEqualTo(Session.State.Crashed)
91-
assertThat(session.isPendingUnhandled).isFalse()
92+
assertThat(session.hasNonTerminatingUnhandledError()).isFalse()
9293
}
9394

9495
@Test
95-
fun `updating to Crashed clears pending unhandled and end stays Crashed`() {
96+
fun `updating to Crashed clears a non-terminating unhandled error and end stays Crashed`() {
9697
val session = okSession()
97-
session.setPendingUnhandled(true)
98+
session.setNonTerminatingUnhandledError(true)
9899

99100
session.update(Session.State.Crashed, null, true)
100101
session.end()
101102

102103
assertThat(session.status).isEqualTo(Session.State.Crashed)
103-
assertThat(session.isPendingUnhandled).isFalse()
104+
assertThat(session.hasNonTerminatingUnhandledError()).isFalse()
104105
}
105106

106107
@Test
107-
fun `clone preserves pending unhandled`() {
108+
fun `clone preserves a non-terminating unhandled error`() {
108109
val session = okSession()
109-
session.setPendingUnhandled(true)
110+
session.setNonTerminatingUnhandledError(true)
110111

111112
val clone = session.clone()
112113

113-
assertThat(clone.isPendingUnhandled).isTrue()
114+
assertThat(clone.hasNonTerminatingUnhandledError()).isTrue()
114115
}
115116

116117
@Test
117-
fun `serialization round-trips pending unhandled and Unhandled status`() {
118+
fun `serialization round-trips a non-terminating unhandled error and Unhandled status`() {
118119
val logger = mock<ILogger>()
119120
val session = okSession()
120-
session.setPendingUnhandled(true)
121+
session.setNonTerminatingUnhandledError(true)
121122
session.end()
122123
assertThat(session.status).isEqualTo(Session.State.Unhandled)
123124

@@ -128,17 +129,17 @@ class SessionTest {
128129
Session.Deserializer().deserialize(JsonObjectReader(StringReader(writer.toString())), logger)
129130

130131
assertThat(deserialized.status).isEqualTo(Session.State.Unhandled)
131-
assertThat(deserialized.isPendingUnhandled).isTrue()
132+
assertThat(deserialized.hasNonTerminatingUnhandledError()).isTrue()
132133
}
133134

134135
@Test
135-
fun `pending unhandled defaults to false and is not serialized when unset`() {
136+
fun `a non-terminating unhandled error defaults to false and is not serialized when unset`() {
136137
val logger = mock<ILogger>()
137138
val session = okSession()
138139

139140
val writer = StringWriter()
140141
session.serialize(JsonObjectWriter(writer, 100), logger)
141142

142-
assertThat(writer.toString()).doesNotContain("pending_unhandled")
143+
assertThat(writer.toString()).doesNotContain("non_terminating_unhandled_error")
143144
}
144145
}

0 commit comments

Comments
 (0)