epics_ca/
context.rs

1use crate::error::{result_from_raw, Error};
2use std::{ops::Deref, ptr::NonNull, sync::Arc};
3
4/// Unique context.
5///
6/// Manages raw EPICS CA context.
7#[derive(Debug)]
8pub struct UniqueContext {
9    raw: NonNull<sys::ca_client_context>,
10}
11
12unsafe impl Send for UniqueContext {}
13
14impl UniqueContext {
15    /// Create a new unique context.
16    pub fn new() -> Result<Self, Error> {
17        let prev = Self::current();
18        if !prev.is_null() {
19            Self::detach();
20        }
21        let ret = result_from_raw(unsafe {
22            sys::ca_context_create(
23                sys::ca_preemptive_callback_select::ca_enable_preemptive_callback,
24            )
25        })
26        .map(|()| {
27            let raw = Self::current();
28            Self::detach();
29            Self {
30                raw: NonNull::new(raw).unwrap(),
31            }
32        });
33        if let Some(prev) = NonNull::new(prev) {
34            Self::attach(prev);
35        }
36        ret
37    }
38    pub(crate) fn current() -> *mut sys::ca_client_context {
39        unsafe { sys::ca_current_context() }
40    }
41    fn attach(raw: NonNull<sys::ca_client_context>) {
42        unsafe { sys::ca_attach_context(raw.as_ptr()) };
43    }
44    fn detach() {
45        unsafe { sys::ca_detach_context() };
46    }
47
48    /// Perform some operation inside of the context.
49    ///
50    /// This calls can be safely nested (either from same context or different ones).
51    pub fn with<F: FnOnce() -> R, R>(&self, f: F) -> R {
52        let prev = Self::current();
53        if prev != self.raw.as_ptr() {
54            if !prev.is_null() {
55                Self::detach();
56            }
57            Self::attach(self.raw);
58        }
59        let ret = f();
60        if prev != self.raw.as_ptr() {
61            Self::detach();
62            if let Some(prev) = NonNull::new(prev) {
63                Self::attach(prev);
64            }
65        }
66        ret
67    }
68
69    /// Flush IO queue.
70    ///
71    /// **Must be called after almost any EPICS CA function to ensure it has an effect.**
72    pub(crate) fn flush_io(&self) {
73        self.with(|| result_from_raw(unsafe { sys::ca_flush_io() }))
74            .unwrap()
75    }
76}
77
78impl Drop for UniqueContext {
79    fn drop(&mut self) {
80        let prev = Self::current();
81        if !prev.is_null() {
82            Self::detach();
83        }
84        Self::attach(self.raw);
85        unsafe { sys::ca_context_destroy() };
86        if let Some(prev) = NonNull::new(prev) {
87            Self::attach(prev);
88        }
89    }
90}
91
92/// Shared context.
93#[derive(Clone, Debug)]
94pub struct Context {
95    arc: Arc<UniqueContext>,
96}
97
98unsafe impl Send for Context {}
99
100impl Deref for Context {
101    type Target = UniqueContext;
102    fn deref(&self) -> &Self::Target {
103        &self.arc
104    }
105}
106
107impl Context {
108    /// Creates a new [`UniqueContext`] and shares it.
109    pub fn new() -> Result<Self, Error> {
110        UniqueContext::new().map(|uniq| Self {
111            arc: Arc::new(uniq),
112        })
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::UniqueContext as Context;
119
120    #[test]
121    fn new() {
122        Context::new().unwrap();
123    }
124
125    #[test]
126    fn attach() {
127        assert!(Context::current().is_null());
128        let ctx = Context::new().unwrap();
129        assert!(Context::current().is_null());
130        ctx.with(|| {
131            assert_eq!(Context::current(), ctx.raw.as_ptr());
132        });
133        assert!(Context::current().is_null());
134    }
135
136    #[test]
137    fn reattach_same() {
138        assert!(Context::current().is_null());
139        let ctx = Context::new().unwrap();
140        assert!(Context::current().is_null());
141        ctx.with(|| {
142            assert_eq!(Context::current(), ctx.raw.as_ptr());
143            ctx.with(|| {
144                assert_eq!(Context::current(), ctx.raw.as_ptr());
145            });
146            assert_eq!(Context::current(), ctx.raw.as_ptr());
147        });
148        assert!(Context::current().is_null());
149    }
150
151    #[test]
152    fn reattach_different() {
153        assert!(Context::current().is_null());
154        let ctx = Context::new().unwrap();
155        assert!(Context::current().is_null());
156        ctx.with(|| {
157            assert_eq!(Context::current(), ctx.raw.as_ptr());
158            let other_ctx = Context::new().unwrap();
159            assert_eq!(Context::current(), ctx.raw.as_ptr());
160            other_ctx.with(|| {
161                assert_eq!(Context::current(), other_ctx.raw.as_ptr());
162                ctx.with(|| {
163                    assert_eq!(Context::current(), ctx.raw.as_ptr());
164                });
165                assert_eq!(Context::current(), other_ctx.raw.as_ptr());
166            });
167            assert_eq!(Context::current(), ctx.raw.as_ptr());
168            drop(other_ctx);
169            assert_eq!(Context::current(), ctx.raw.as_ptr());
170        });
171        assert!(Context::current().is_null());
172    }
173}