Skip to content

Commit 1d57db4

Browse files
authored
feat(thread-protocol): Extract lock mechanism (#1979)
The android sdk is going to start sending lock mechanism as a part of the thread interface which we are going to want to extract and store on events json. ![image (2)](https://user-images.githubusercontent.com/63818634/228589578-de6aae22-7e38-4220-958a-c184fe421fc5.png) closes getsentry/sentry#46510
1 parent 5346692 commit 1d57db4

File tree

3 files changed

+245
-1
lines changed

3 files changed

+245
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Metrics:
3232
- Allow monitor checkins to paass `monitor_config` for monitor upserts. ([#1962](https://github.com/getsentry/relay/pull/1962))
3333
- Changes how device class is determined for iPhone devices. Instead of checking processor frequency, the device model is mapped to a device class. ([#1970](https://github.com/getsentry/relay/pull/1970))
3434
- Don't sanitize transactions if no clustering rules exist and no UUIDs were scrubbed. ([#1976](https://github.com/getsentry/relay/pull/1976))
35+
- Add `thread.lock_mechanism` field to protocol. ([#1979](https://github.com/getsentry/relay/pull/1979))
3536

3637
**Internal**:
3738

relay-general/src/protocol/thread.rs

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize, Serializer};
33
use crate::processor::ProcessValue;
44
use crate::protocol::{RawStacktrace, Stacktrace};
55
use crate::types::{
6-
Annotated, Empty, Error, FromValue, IntoValue, Object, SkipSerialization, Value,
6+
Annotated, Empty, Error, ErrorKind, FromValue, IntoValue, Object, SkipSerialization, Value,
77
};
88

99
/// Represents a thread id.
@@ -69,6 +69,111 @@ impl Empty for ThreadId {
6969
}
7070
}
7171

72+
/// Possible lock types responsible for a thread's blocked state
73+
#[derive(Debug, Copy, Clone, Eq, PartialEq, ProcessValue, Empty)]
74+
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
75+
#[cfg_attr(feature = "jsonschema", schemars(rename_all = "lowercase"))]
76+
pub enum LockReasonType {
77+
/// Thread is Runnable but holding a lock object (generic case).
78+
Locked = 1,
79+
/// Thread TimedWaiting in Object.wait() with a timeout.
80+
Waiting = 2,
81+
/// Thread TimedWaiting in Thread.sleep().
82+
Sleeping = 4,
83+
/// Thread Blocked on a monitor/shared lock.
84+
Blocked = 8,
85+
// This enum does not have a `fallback_variant` because we consider it unlikely to be extended. If it is,
86+
// The error added to `Meta` will tell us to update this enum.
87+
}
88+
89+
impl LockReasonType {
90+
fn from_android_lock_reason_type(value: u64) -> Option<LockReasonType> {
91+
Some(match value {
92+
1 => LockReasonType::Locked,
93+
2 => LockReasonType::Waiting,
94+
4 => LockReasonType::Sleeping,
95+
8 => LockReasonType::Blocked,
96+
_ => return None,
97+
})
98+
}
99+
}
100+
101+
impl FromValue for LockReasonType {
102+
fn from_value(value: Annotated<Value>) -> Annotated<Self> {
103+
match value {
104+
Annotated(Some(Value::U64(val)), mut meta) => {
105+
match LockReasonType::from_android_lock_reason_type(val) {
106+
Some(value) => Annotated(Some(value), meta),
107+
None => {
108+
meta.add_error(ErrorKind::InvalidData);
109+
meta.set_original_value(Some(val));
110+
Annotated(None, meta)
111+
}
112+
}
113+
}
114+
Annotated(Some(Value::I64(val)), mut meta) => {
115+
match LockReasonType::from_android_lock_reason_type(val as u64) {
116+
Some(value) => Annotated(Some(value), meta),
117+
None => {
118+
meta.add_error(ErrorKind::InvalidData);
119+
meta.set_original_value(Some(val));
120+
Annotated(None, meta)
121+
}
122+
}
123+
}
124+
Annotated(None, meta) => Annotated(None, meta),
125+
Annotated(Some(value), mut meta) => {
126+
meta.add_error(Error::expected("lock reason type"));
127+
meta.set_original_value(Some(value));
128+
Annotated(None, meta)
129+
}
130+
}
131+
}
132+
}
133+
134+
impl IntoValue for LockReasonType {
135+
fn into_value(self) -> Value {
136+
Value::U64(self as u64)
137+
}
138+
139+
fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
140+
where
141+
Self: Sized,
142+
S: Serializer,
143+
{
144+
Serialize::serialize(&(*self as u64), s)
145+
}
146+
}
147+
148+
/// Represents an instance of a held lock (java monitor object) in a thread.
149+
#[derive(Clone, Debug, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
150+
#[cfg_attr(feature = "jsonschema", derive(JsonSchema))]
151+
pub struct LockReason {
152+
/// Type of lock on the thread with available options being blocked, waiting, sleeping and locked.
153+
#[metastructure(field = "type", required = "true")]
154+
pub ty: Annotated<LockReasonType>,
155+
156+
/// Address of the java monitor object.
157+
#[metastructure(skip_serialization = "empty")]
158+
pub address: Annotated<String>,
159+
160+
/// Package name of the java monitor object.
161+
#[metastructure(skip_serialization = "empty")]
162+
pub package_name: Annotated<String>,
163+
164+
/// Class name of the java monitor object.
165+
#[metastructure(skip_serialization = "empty")]
166+
pub class_name: Annotated<String>,
167+
168+
/// Thread ID that's holding the lock.
169+
#[metastructure(skip_serialization = "empty")]
170+
pub thread_id: Annotated<ThreadId>,
171+
172+
/// Additional arbitrary fields for forwards compatibility.
173+
#[metastructure(additional_properties)]
174+
pub other: Object<Value>,
175+
}
176+
72177
/// A process thread of an event.
73178
///
74179
/// The Threads Interface specifies threads that were running at the time an event happened. These threads can also contain stack traces.
@@ -128,6 +233,10 @@ pub struct Thread {
128233
#[metastructure(skip_serialization = "empty")]
129234
pub state: Annotated<String>,
130235

236+
/// Represents an instance of a held lock (java monitor object) in a thread.
237+
#[metastructure(skip_serialization = "empty")]
238+
pub lock_reason: Annotated<LockReason>,
239+
131240
/// Additional arbitrary fields for forwards compatibility.
132241
#[metastructure(additional_properties)]
133242
pub other: Object<Value>,
@@ -183,6 +292,7 @@ mod tests {
183292
current: Annotated::new(true),
184293
main: Annotated::new(true),
185294
state: Annotated::new("RUNNABLE".to_string()),
295+
lock_reason: Annotated::empty(),
186296
other: {
187297
let mut map = Map::new();
188298
map.insert(
@@ -205,4 +315,54 @@ mod tests {
205315
assert_eq!(thread, Annotated::from_json(json).unwrap());
206316
assert_eq!(json, thread.to_json_pretty().unwrap());
207317
}
318+
319+
#[test]
320+
fn test_thread_lock_reason_roundtrip() {
321+
// stack traces are tested separately
322+
let input = r#"{
323+
"id": 42,
324+
"name": "myname",
325+
"crashed": true,
326+
"current": true,
327+
"main": true,
328+
"state": "BLOCKED",
329+
"lock_reason": {
330+
"type": 1,
331+
"package_name": "android.database.sqlite",
332+
"class_name": "SQLiteConnection",
333+
"thread_id": 2
334+
},
335+
"other": "value"
336+
}"#;
337+
let thread = Annotated::new(Thread {
338+
id: Annotated::new(ThreadId::Int(42)),
339+
name: Annotated::new("myname".to_string()),
340+
stacktrace: Annotated::empty(),
341+
raw_stacktrace: Annotated::empty(),
342+
crashed: Annotated::new(true),
343+
current: Annotated::new(true),
344+
main: Annotated::new(true),
345+
state: Annotated::new("BLOCKED".to_string()),
346+
lock_reason: Annotated::new(LockReason {
347+
ty: Annotated::new(LockReasonType::Locked),
348+
address: Annotated::empty(),
349+
package_name: Annotated::new("android.database.sqlite".to_string()),
350+
class_name: Annotated::new("SQLiteConnection".to_string()),
351+
thread_id: Annotated::new(ThreadId::Int(2)),
352+
other: Default::default(),
353+
}),
354+
other: {
355+
let mut map = Map::new();
356+
map.insert(
357+
"other".to_string(),
358+
Annotated::new(Value::String("value".to_string())),
359+
);
360+
map
361+
},
362+
});
363+
364+
assert_eq!(thread, Annotated::from_json(input).unwrap());
365+
366+
assert_eq!(input, thread.to_json_pretty().unwrap());
367+
}
208368
}

relay-general/tests/snapshots/test_fixtures__event_schema.snap

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,77 @@ expression: "relay_general::protocol::event_json_schema()"
20222022
"fatal"
20232023
]
20242024
},
2025+
"LockReason": {
2026+
"description": " Represents an instance of a held lock (java monitor object) in a thread.",
2027+
"anyOf": [
2028+
{
2029+
"type": "object",
2030+
"required": [
2031+
"type"
2032+
],
2033+
"properties": {
2034+
"address": {
2035+
"description": " Address of the java monitor object.",
2036+
"default": null,
2037+
"type": [
2038+
"string",
2039+
"null"
2040+
]
2041+
},
2042+
"class_name": {
2043+
"description": " Class name of the java monitor object.",
2044+
"default": null,
2045+
"type": [
2046+
"string",
2047+
"null"
2048+
]
2049+
},
2050+
"package_name": {
2051+
"description": " Package name of the java monitor object.",
2052+
"default": null,
2053+
"type": [
2054+
"string",
2055+
"null"
2056+
]
2057+
},
2058+
"thread_id": {
2059+
"description": " Thread ID that's holding the lock.",
2060+
"default": null,
2061+
"anyOf": [
2062+
{
2063+
"$ref": "#/definitions/ThreadId"
2064+
},
2065+
{
2066+
"type": "null"
2067+
}
2068+
]
2069+
},
2070+
"type": {
2071+
"description": " Type of lock on the thread with available options being blocked, waiting, sleeping and locked.",
2072+
"anyOf": [
2073+
{
2074+
"$ref": "#/definitions/LockReasonType"
2075+
},
2076+
{
2077+
"type": "null"
2078+
}
2079+
]
2080+
}
2081+
},
2082+
"additionalProperties": false
2083+
}
2084+
]
2085+
},
2086+
"LockReasonType": {
2087+
"description": "Possible lock types responsible for a thread's blocked state",
2088+
"type": "string",
2089+
"enum": [
2090+
"locked",
2091+
"waiting",
2092+
"sleeping",
2093+
"blocked"
2094+
]
2095+
},
20252096
"LogEntry": {
20262097
"description": " A log entry message.\n\n A log message is similar to the `message` attribute on the event itself but\n can additionally hold optional parameters.\n\n ```json\n {\n \"message\": {\n \"message\": \"My raw message with interpreted strings like %s\",\n \"params\": [\"this\"]\n }\n }\n ```\n\n ```json\n {\n \"message\": {\n \"message\": \"My raw message with interpreted strings like {foo}\",\n \"params\": {\"foo\": \"this\"}\n }\n }\n ```",
20272098
"anyOf": [
@@ -3126,6 +3197,18 @@ expression: "relay_general::protocol::event_json_schema()"
31263197
}
31273198
]
31283199
},
3200+
"lock_reason": {
3201+
"description": " Represents an instance of a held lock (java monitor object) in a thread.",
3202+
"default": null,
3203+
"anyOf": [
3204+
{
3205+
"$ref": "#/definitions/LockReason"
3206+
},
3207+
{
3208+
"type": "null"
3209+
}
3210+
]
3211+
},
31293212
"main": {
31303213
"description": " A flag indicating whether the thread was responsible for rendering the user interface.",
31313214
"default": null,

0 commit comments

Comments
 (0)