use std::mem;
use std::fmt;
use std::thread;
use std::cell::RefCell;
use std::sync::{Arc, RwLock};
use std::borrow::Cow;
use api::protocol::{Breadcrumb, Context, User, Value};
use client::Client;
use utils;
use im;
lazy_static! {
static ref PROCESS_STACK: RwLock<Stack> = RwLock::new(Stack::for_process());
}
thread_local! {
static THREAD_STACK: RefCell<Stack> = RefCell::new(Stack::for_thread());
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StackType {
Process,
Thread,
}
#[derive(Debug)]
pub struct Stack {
layers: Vec<StackLayer>,
ty: StackType,
}
#[derive(PartialEq, Clone, Copy)]
struct StackLayerToken(*const Stack, usize);
#[allow(unused)]
pub(crate) fn scope_panic_safe() -> bool {
PROCESS_STACK.try_read().is_ok() && THREAD_STACK.with(|x| x.try_borrow().is_ok())
}
#[derive(Debug, Clone)]
pub struct Scope {
pub(crate) fingerprint: Option<Arc<Vec<Cow<'static, str>>>>,
pub(crate) transaction: Option<Arc<String>>,
pub(crate) breadcrumbs: im::Vector<Breadcrumb>,
pub(crate) user: Option<Arc<User>>,
pub(crate) extra: im::HashMap<String, Value>,
pub(crate) tags: im::HashMap<String, String>,
pub(crate) contexts: im::HashMap<String, Context>,
}
fn default_scope() -> Scope {
Scope {
fingerprint: None,
transaction: None,
breadcrumbs: Default::default(),
user: None,
extra: Default::default(),
tags: Default::default(),
contexts: {
let mut contexts = im::HashMap::new();
if let Some(c) = utils::os_context() {
contexts = contexts.insert("os".to_string(), c);
}
if let Some(c) = utils::rust_context() {
contexts = contexts.insert("rust".to_string(), c);
}
if let Some(c) = utils::device_context() {
contexts = contexts.insert("device".to_string(), c);
}
contexts
},
}
}
#[derive(Debug, Clone)]
struct StackLayer {
client: Option<Arc<Client>>,
scope: Scope,
}
impl Stack {
pub fn for_process() -> Stack {
Stack {
layers: vec![
StackLayer {
client: None,
scope: default_scope(),
},
],
ty: StackType::Process,
}
}
pub fn for_thread() -> Stack {
Stack {
layers: vec![
with_process_stack(|stack| StackLayer {
client: stack.client(),
scope: stack.scope().clone(),
}),
],
ty: StackType::Thread,
}
}
pub fn push(&mut self) {
let scope = self.layers[self.layers.len() - 1].clone();
self.layers.push(scope);
}
pub fn pop(&mut self) {
if self.layers.len() <= 1 {
panic!("Pop from empty {:?} stack", self.ty)
}
self.layers.pop().unwrap();
}
pub fn bind_client(&mut self, client: Option<Arc<Client>>) {
let depth = self.layers.len() - 1;
self.layers[depth].client = client;
}
pub fn client(&self) -> Option<Arc<Client>> {
self.layers[self.layers.len() - 1].client.clone()
}
pub fn scope(&self) -> &Scope {
let idx = self.layers.len() - 1;
&self.layers[idx].scope
}
pub fn scope_mut(&mut self) -> &mut Scope {
let idx = self.layers.len() - 1;
&mut self.layers[idx].scope
}
fn layer_token(&self) -> StackLayerToken {
StackLayerToken(self as *const Stack, self.layers.len())
}
}
fn is_main_thread() -> bool {
let thread = thread::current();
let raw_id: u64 = unsafe { mem::transmute(thread.id()) };
raw_id == 0
}
fn with_process_stack<F, R>(f: F) -> R
where
F: FnOnce(&Stack) -> R,
{
f(&mut PROCESS_STACK.read().unwrap_or_else(|x| x.into_inner()))
}
fn with_process_stack_mut<F, R>(f: F) -> R
where
F: FnOnce(&mut Stack) -> R,
{
f(&mut PROCESS_STACK.write().unwrap_or_else(|x| x.into_inner()))
}
fn with_stack_mut<F, R>(f: F) -> R
where
F: FnOnce(&mut Stack) -> R,
{
if is_main_thread() {
with_process_stack_mut(f)
} else {
THREAD_STACK.with(|stack| f(&mut *stack.borrow_mut()))
}
}
fn with_stack<F, R>(f: F) -> R
where
F: FnOnce(&Stack) -> R,
{
if is_main_thread() {
with_process_stack(f)
} else {
THREAD_STACK.with(|stack| f(&*stack.borrow()))
}
}
pub fn with_client_and_scope<F, R>(f: F) -> R
where
F: FnOnce(Arc<Client>, &Scope) -> R,
R: Default,
{
with_stack(|stack| {
if let Some(client) = stack.client() {
f(client, stack.scope())
} else {
Default::default()
}
})
}
pub(crate) fn with_client_and_scope_mut<F, R>(f: F) -> R
where
F: FnOnce(Arc<Client>, &mut Scope) -> R,
R: Default,
{
with_stack_mut(|stack| {
if let Some(client) = stack.client() {
f(client, stack.scope_mut())
} else {
Default::default()
}
})
}
#[derive(Default)]
pub struct ScopeGuard(Option<StackLayerToken>);
impl fmt::Debug for ScopeGuard {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ScopeGuard")
}
}
impl Drop for ScopeGuard {
fn drop(&mut self) {
if let Some(token) = self.0 {
with_stack_mut(|stack| {
if stack.layer_token() != token {
panic!("Current active stack does not match scope guard");
} else {
stack.pop();
}
})
}
}
}
pub fn push_scope() -> ScopeGuard {
with_stack_mut(|stack| {
stack.push();
ScopeGuard(Some(stack.layer_token()))
})
}
pub fn current_client() -> Option<Arc<Client>> {
with_client_impl! {{
with_stack(|stack| stack.client())
}}
}
pub fn bind_client(client: Arc<Client>) {
with_client_impl! {{
with_stack_mut(|stack| stack.bind_client(Some(client)));
}}
}
pub fn unbind_client() {
with_client_impl! {{
with_stack_mut(|stack| stack.bind_client(None));
}}
}
impl Scope {
pub fn clear(&mut self) {
*self = default_scope();
}
pub fn set_fingerprint(&mut self, fingerprint: Option<&[&str]>) {
self.fingerprint =
fingerprint.map(|fp| Arc::new(fp.iter().map(|x| Cow::Owned(x.to_string())).collect()))
}
pub fn set_transaction(&mut self, transaction: Option<&str>) {
self.transaction = transaction.map(|txn| Arc::new(txn.to_string()));
}
pub fn set_user(&mut self, user: Option<User>) {
self.user = user.map(Arc::new);
}
pub fn set_tag<V: ToString>(&mut self, key: &str, value: V) {
self.tags = self.tags.insert(key.to_string(), value.to_string());
}
pub fn remove_tag(&mut self, key: &str) {
self.tags = self.tags.remove(&key.to_string());
}
pub fn set_context<C: Into<Context>>(&mut self, key: &str, value: C) {
self.contexts = self.contexts.insert(key.to_string(), value.into());
}
pub fn remove_context(&mut self, key: &str) {
self.contexts = self.contexts.remove(&key.to_string());
}
pub fn set_extra(&mut self, key: &str, value: Value) {
self.extra = self.extra.insert(key.to_string(), value);
}
pub fn remove_extra(&mut self, key: &str) {
self.extra = self.extra.remove(&key.to_string());
}
}