rustler_elixir_fun/
lib.rs1use rustler::*;
2use rustler::types::LocalPid;
3use rustler::types::tuple::make_tuple;
4use rustler::error::Error;
5use rustler_sys;
6use std::mem::MaybeUninit;
7use rustler::wrapper::ErlNifPid;
8use std::sync::{Mutex, Condvar};
9use std::time::Duration;
10use rustler_stored_term::StoredTerm;
11use rustler_stored_term::StoredTerm::{AnAtom, Tuple};
12
13use crate::ElixirFunCallResult::*;
14
15pub struct ManualFuture {
16 mutex: Mutex<Option<StoredTerm>>,
17 cond: Condvar,
18}
19
20impl ManualFuture {
21 pub fn new() -> ManualFuture {
22 ManualFuture {mutex: Mutex::new(None), cond: Condvar::new()}
23 }
24
25 pub fn wait_until_filled(& self, timeout: Duration) -> Option<StoredTerm> {
26 let (mut guard, wait_timeout_result) = self.cond.wait_timeout_while(
27 self.mutex.lock().unwrap(),
28 timeout,
29 |pending| { pending.is_none() }
30 ).expect("ManualFuture's Mutex was unexpectedly poisoned");
31 if wait_timeout_result.timed_out() {
32 None
33 } else {
34 let val = guard.take().unwrap();
35 Some(val)
36 }
37 }
38 pub fn fill(&self, value: StoredTerm) {
39 let mut started = self.mutex.lock().unwrap();
40 *started = Some(value);
41 self.cond.notify_all();
42 }
43}
44
45mod atoms {
51 rustler::atoms! {
52 ok,
53 error,
54 exception,
55 exit,
56 throw,
57 timeout,
58 }
59}
60
61pub fn whereis_pid<'a>(env: Env<'a>, name: Term<'a>) -> Result<LocalPid, Error> {
67 let mut enif_pid = MaybeUninit::uninit();
68
69 if unsafe { rustler_sys::enif_whereis_pid(env.as_c_arg(), name.as_c_arg(), enif_pid.as_mut_ptr()) } == 0 {
70 Err(Error::Term(Box::new("No pid registered under the given name.")))
71 } else {
72 let enif_pid = unsafe {enif_pid.assume_init()};
74
75 let pid = unsafe { std::mem::transmute::<ErlNifPid, LocalPid>(enif_pid) };
78 Ok(pid)
79 }
80}
81
82fn send_to_elixir<'a>(env: Env<'a>, pid: Term<'a>, value: Term<'a>) -> Result<(), Error> {
83 let pid : LocalPid = pid.decode().or_else(|_| whereis_pid(env, pid))?;
84
85 env.send(&pid, value);
86 Ok(())
87}
88
89#[derive(Clone)]
90pub enum ElixirFunCallResult {
100 Success(StoredTerm),
102 ExceptionRaised(StoredTerm),
104 Exited(StoredTerm),
106 ValueThrown(StoredTerm),
108 TimedOut,
110}
111
112impl Encoder for ElixirFunCallResult {
113 fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
114 let result = match self {
115 Success(term) => Ok(term),
116 ExceptionRaised(term) => Err(make_tuple(env, &[atoms::exception().to_term(env), term.encode(env)])),
117 Exited(term) => Err(make_tuple(env, &[atoms::exit().to_term(env), term.encode(env)])),
118 ValueThrown(term) => Err(make_tuple(env, &[atoms::throw().to_term(env), term.encode(env)])),
119 TimedOut => Err(atoms::timeout().to_term(env))
120 };
121
122 result.encode(env)
123 }
124}
125
126pub fn apply_elixir_fun<'a>(env: Env<'a>, pid_or_name: Term<'a>, fun: Term<'a>, parameters: Term<'a>) -> Result<ElixirFunCallResult, Error> {
142 apply_elixir_fun_timeout(env, pid_or_name, fun, parameters, Duration::from_millis(5000))
143}
144
145pub fn apply_elixir_fun_timeout<'a>(env: Env<'a>, pid_or_name: Term<'a>, fun: Term<'a>, parameters: Term<'a>, timeout: Duration) -> Result<ElixirFunCallResult, Error> {
147 if !fun.is_fun() {
148 return Err(Error::BadArg)
149 }
150
151 if !parameters.is_list() {
152 return Err(Error::BadArg)
153 }
154
155 let future = ManualFuture::new();
158 let future_ptr : *const ManualFuture = &future;
159 let raw_future_ptr = future_ptr as usize;
160 let fun_tuple = rustler::types::tuple::make_tuple(env, &[fun, parameters, raw_future_ptr.encode(env)]);
161 send_to_elixir(env, pid_or_name, fun_tuple)?;
162
163 match future.wait_until_filled(timeout) {
164 None => Ok(TimedOut),
165 Some(result) => Ok(parse_fun_call_result(env, result))
166 }
167}
168
169fn parse_fun_call_result<'a>(env: Env<'a>, result: StoredTerm) -> ElixirFunCallResult {
170 match result {
171 Tuple(ref tuple) =>
172 match &tuple[..] {
173 [AnAtom(ok), value] if ok == &rustler::types::atom::ok() => Success(value.clone()),
174 [AnAtom(error), Tuple(ref error_tuple)] if error == &atoms::error() => {
175 match &error_tuple[..] {
176 [AnAtom(exception), problem] if exception == &atoms::exception() => ExceptionRaised(problem.clone()),
177 [AnAtom(exit), problem] if exit == &atoms::exit() => Exited(problem.clone()),
178 [AnAtom(throw), problem] if throw == &atoms::throw() => ValueThrown(problem.clone()),
179 _ => panic!("RustlerElixirFun's function wrapper returned an unexpected error tuple result: {:?}", result.encode(env))
180 }
181 },
182 _ => panic!("RustlerElixirFun's function wrapper returned an unexpected tuple result: {:?}", result.encode(env))
183 },
184 _ => panic!("RustlerElixirFun's function wrapper returned an unexpected result: {:?}", result.encode(env))
185 }
186}