Skip to content

Commit

Permalink
feat(runtime): add extension to the Result type that allows retrying …
Browse files Browse the repository at this point in the history
…and waiting for a correct result
  • Loading branch information
Wodann committed Oct 18, 2019
1 parent 69b3582 commit aa5e789
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 49 deletions.
3 changes: 2 additions & 1 deletion crates/mun/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[macro_use]
extern crate failure;

use std::error::Error;
use std::time::Duration;

use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
Expand Down Expand Up @@ -88,7 +89,7 @@ fn start(matches: &ArgMatches) -> Result<(), failure::Error> {
let entry_point = matches.value_of("entry").unwrap_or("main");

#[allow(clippy::unit_arg)]
Ok(invoke_fn!(runtime, entry_point))
invoke_fn!(runtime, entry_point).map_err(|e| failure::err_msg(e.description().to_string()))
}

fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOptions, failure::Error> {
Expand Down
13 changes: 8 additions & 5 deletions crates/mun_runtime/src/assembly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ use mun_abi::AssemblyInfo;

const LIB_DIR: &str = "tmp";

/// An assembly is the smallest compilable unit of code in Mun.
/// An assembly is a hot reloadable compilation unit, consisting of one or more Mun modules.
pub struct Assembly {
library_path: PathBuf,
library: Option<Library>,
info: AssemblyInfo,
}

impl Assembly {
/// Loads an assembly for the library at `library_path` and its dependencies.
/// Loads an assembly and its information for the shared library at `library_path`.
pub fn load(
library_path: &Path,
runtime_dispatch_table: &mut DispatchTable,
Expand Down Expand Up @@ -55,6 +55,7 @@ impl Assembly {
})
}

/// Links the assembly using the runtime's dispatch table.
pub fn link(&mut self, runtime_dispatch_table: &DispatchTable) -> Result<(), Error> {
for (dispatch_ptr, fn_signature) in self.info.dispatch_table.iter_mut() {
let fn_ptr = runtime_dispatch_table
Expand All @@ -73,14 +74,16 @@ impl Assembly {
Ok(())
}

/// Swaps the assembly's shared library and its information for the library at `library_path`.
pub fn swap(
&mut self,
library_path: &Path,
runtime_dispatch_table: &mut DispatchTable,
) -> Result<(), Error> {
// let library_path = library_path.canonicalize()?;

// Drop the old library, as some operating systems don't allow editing of in-use shared libraries
// Drop the old library, as some operating systems don't allow editing of in-use shared
// libraries
self.library.take();

for function in self.info.symbols.functions() {
Expand All @@ -92,12 +95,12 @@ impl Assembly {
Ok(())
}

/// Retrieves the assembly's loaded shared library.
/// Returns the assembly's information.
pub fn info(&self) -> &AssemblyInfo {
&self.info
}

/// Returns the path corresponding tot the assembly's library.
/// Returns the path corresponding to the assembly's library.
pub fn library_path(&self) -> &Path {
self.library_path.as_path()
}
Expand Down
43 changes: 27 additions & 16 deletions crates/mun_runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl RuntimeBuilder {
}
}

/// A runtime dispatch table that maps full function paths to function information.
pub struct DispatchTable {
functions: HashMap<String, FunctionInfo>,
}
Expand Down Expand Up @@ -155,27 +156,37 @@ impl MunRuntime {
}
}

/// Extends a result object with functions that allow retrying of an action.
pub trait Retriable: Sized {
type Output;

/// Retries an action, resulting in a potentially mutated version of itself.
fn retry(self) -> Self;

/// Keeps retrying the same action until it succeeds, resulting in an output.
fn wait(self) -> Self::Output;
}

invoke_fn_impl! {
fn invoke_fn0();
fn invoke_fn1(a: A);
fn invoke_fn2(a: A, b: B);
fn invoke_fn3(a: A, b: B, c: C);
fn invoke_fn4(a: A, b: B, c: C, d: D);
fn invoke_fn5(a: A, b: B, c: C, d: D, e: E);
fn invoke_fn6(a: A, b: B, c: C, d: D, e: E, f: F);
fn invoke_fn7(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
fn invoke_fn8(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
fn invoke_fn9(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
fn invoke_fn10(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
fn invoke_fn11(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
fn invoke_fn12(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);
fn invoke_fn0() -> InvokeErr0;
fn invoke_fn1(a: A) -> InvokeErr1;
fn invoke_fn2(a: A, b: B) -> InvokeErr2;
fn invoke_fn3(a: A, b: B, c: C) -> InvokeErr3;
fn invoke_fn4(a: A, b: B, c: C, d: D) -> InvokeErr4;
fn invoke_fn5(a: A, b: B, c: C, d: D, e: E) -> InvokeErr5;
fn invoke_fn6(a: A, b: B, c: C, d: D, e: E, f: F) -> InvokeErr6;
fn invoke_fn7(a: A, b: B, c: C, d: D, e: E, f: F, g: G) -> InvokeErr7;
fn invoke_fn8(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) -> InvokeErr8;
fn invoke_fn9(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) -> InvokeErr9;
fn invoke_fn10(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) -> InvokeErr10;
fn invoke_fn11(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K) -> InvokeErr11;
fn invoke_fn12(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L) -> InvokeErr12;
}

#[cfg(all(test, windows))]
mod tests {
use super::{invoke_fn, MunRuntime, RuntimeBuilder};
use super::{invoke_fn, RuntimeBuilder};
use std::path::PathBuf;
use std::time::Duration;

fn test_lib_path() -> PathBuf {
use std::env;
Expand All @@ -198,7 +209,7 @@ mod tests {
let a: f64 = 4.0;
let b: f64 = 2.0;

let result: f64 = invoke_fn!(runtime, "add", a, b);
let result: f64 = invoke_fn!(runtime, "add", a, b).unwrap();

assert_eq!(result, a + b);
}
Expand Down
111 changes: 84 additions & 27 deletions crates/mun_runtime/src/macros.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,95 @@
macro_rules! invoke_fn_impl {
($(
fn $FnName:ident($($Arg:tt: $T:ident),*);
fn $FnName:ident($($Arg:tt: $T:ident),*) -> $ErrName:ident;
)+) => {
$(
/// An invocation error that contains the function name, a mutable reference to the
/// runtime, passed arguments, and the output type. This allows the caller to retry
/// the function invocation using the `Retriable` trait.
pub struct $ErrName<'r, 's, $($T: Reflection,)* Output:Reflection> {
msg: String,
runtime: &'r mut MunRuntime,
function_name: &'s str,
$($Arg: $T,)*
output: core::marker::PhantomData<Output>,
}

impl<'r, 's, $($T: Reflection,)* Output: Reflection> core::fmt::Debug for $ErrName<'r, 's, $($T,)* Output> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", &self.msg)
}
}

impl<'r, 's, $($T: Reflection,)* Output: Reflection> core::fmt::Display for $ErrName<'r, 's, $($T,)* Output> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", &self.msg)
}
}

impl<'r, 's, $($T: Reflection,)* Output: Reflection> std::error::Error for $ErrName<'r, 's, $($T,)* Output> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

impl<'r, 's, $($T: Reflection,)* Output: Reflection> $ErrName<'r, 's, $($T,)* Output> {
/// Constructs a new invocation error.
pub fn new(err_msg: String, runtime: &'r mut MunRuntime, function_name: &'s str, $($Arg: $T),*) -> Self {
Self {
msg: err_msg,
runtime,
function_name,
$($Arg,)*
output: core::marker::PhantomData,
}
}
}

impl<'r, 's, $($T: Reflection,)* Output: Reflection> $crate::Retriable for core::result::Result<Output, $ErrName<'r, 's, $($T,)* Output>> {
type Output = Output;

fn retry(self) -> Self {
match self {
Ok(output) => Ok(output),
Err(err) => {
eprintln!("{}", err.msg);
while !err.runtime.update() {
// Wait until there has been an update that might fix the error
}
err.runtime.$FnName(err.function_name, $(err.$Arg,)*)
}
}
}

fn wait(mut self) -> Self::Output {
loop {
if let Ok(output) = self {
return output;
}
self = self.retry();
}
}
}

impl MunRuntime {
/// Invokes the method `method_name` with arguments `args`, in the library compiled based on
/// the manifest at `manifest_path`.
/// Invokes the method `method_name` with arguments `args`, in the library compiled
/// based on the manifest at `manifest_path`.
///
/// If an error occurs when invoking the method, an error message is logged. The runtime
/// continues looping until the cause of the error has been resolved.
pub fn $FnName<$($T: Reflection,)* Output: Reflection>(
&mut self,
function_name: &str,
/// If an error occurs when invoking the method, an error message is logged. The
/// runtime continues looping until the cause of the error has been resolved.
pub fn $FnName<'r, 's, $($T: Reflection,)* Output: Reflection>(
&'r mut self,
function_name: &'s str,
$($Arg: $T,)*
) -> Output {
// Initialize `updated` to `true` to guarantee the method is run at least once
let mut updated = true;
loop {
if updated {
let function: core::result::Result<fn($($T),*) -> Output, String> = self
.get_function_info(function_name)
.ok_or(format!("Failed to obtain function '{}'", function_name))
.and_then(|function| mun_abi::downcast_fn!(function, fn($($T),*) -> Output));
) -> core::result::Result<Output, $ErrName<'r, 's, $($T,)* Output>> {
let function: core::result::Result<fn($($T),*) -> Output, String> = self
.get_function_info(function_name)
.ok_or(format!("Failed to obtain function '{}'", function_name))
.and_then(|function| mun_abi::downcast_fn!(function, fn($($T),*) -> Output));

match function {
Ok(function) => return function($($Arg),*),
Err(ref e) => {
eprintln!("{}", e);
updated = false;
}
}
} else {
updated = self.update();
}
match function {
Ok(function) => Ok(function($($Arg),*)),
Err(e) => Err($ErrName::new(e, self, function_name, $($Arg),*)),
}
}
}
Expand Down

0 comments on commit aa5e789

Please sign in to comment.