diff --git a/build.sbt b/build.sbt index 581558e..f3c104c 100644 --- a/build.sbt +++ b/build.sbt @@ -52,9 +52,9 @@ lazy val root = project }, cargoBuild := { println(Seq("sh", "-c", "cd glue; cargo rustc -- -Awarnings") !!) - println( - Seq("sh", "-c", "cd glue; cargo test --quiet") !! - ) // very important: test suite validates interfaces + //println( + // Seq("sh", "-c", "cd glue; RUST_BACKTRACE=full cargo test --quiet --features e2e") !! + //) // very important: test suite validates interfaces }, fullBuild := { cargoBuild.value diff --git a/glue/Cargo.lock b/glue/Cargo.lock index b5562d6..7d498b0 100644 --- a/glue/Cargo.lock +++ b/glue/Cargo.lock @@ -571,6 +571,7 @@ version = "0.1.0" dependencies = [ "jni", "nb", + "no-std-net", "tinyvec", "toad", "toad-jni", @@ -580,8 +581,6 @@ dependencies = [ [[package]] name = "toad-jni" version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d47119dc7ecc1c65fa860a7b2c95dd45b177d07331ee8a18cac0cc41c9bd825" dependencies = [ "jni", "toad-array 0.5.0", diff --git a/glue/Cargo.toml b/glue/Cargo.toml index 836189c..134605a 100644 --- a/glue/Cargo.toml +++ b/glue/Cargo.toml @@ -7,10 +7,15 @@ publish = false [lib] crate_type = ["cdylib"] +[features] +default = ["e2e"] +e2e = [] + [dependencies] jni = "0.21.1" nb = "1" toad = "0.17.3" -toad-jni = "0.5.1" +toad-jni = {path = "../../toad/toad-jni"} # "0.6.0" +no-std-net = "0.6" toad-msg = "0.18.1" tinyvec = {version = "1.5", default_features = false, features = ["rustc_1_55"]} diff --git a/glue/src/e2e.rs b/glue/src/e2e.rs new file mode 100644 index 0000000..e24db31 --- /dev/null +++ b/glue/src/e2e.rs @@ -0,0 +1,35 @@ +use std::sync::Once; + +use no_std_net::SocketAddr; +use toad::platform::Platform; +use toad_jni::java::{self, Object}; +use toad::net::Addrd; +use toad_msg::{Type, Id, Token, alloc::Message, Code}; + +use crate::{runtime::Runtime, runtime_config::RuntimeConfig}; + +pub fn runtime_init<'a>() -> (Runtime, java::Env<'a>) { + let mut _env = crate::test::init(); + let env = &mut _env; + + let cfg = RuntimeConfig::new(env); + let runtime = Runtime::get_or_init(env, cfg); + (runtime, _env) +} + +fn runtime_poll_req(runtime: &Runtime, env: &mut java::Env) { + assert!(runtime.poll_req(env).is_none()); + + let client = crate::Runtime::try_new("0.0.0.0:5684", Default::default()).unwrap(); + let request = Message::new(Type::Con, Code::GET, Id(0), Token(Default::default())); + client.send_msg(Addrd(request, "0.0.0.0:5683".parse().unwrap())).unwrap(); + + assert!(runtime.poll_req(env).is_some()); +} + +#[test] +fn e2e_test_suite() { + let (runtime, mut env) = runtime_init(); + runtime_poll_req(&runtime, &mut env); +} + diff --git a/glue/src/lib.rs b/glue/src/lib.rs index 0c874dc..185e067 100644 --- a/glue/src/lib.rs +++ b/glue/src/lib.rs @@ -1,5 +1,10 @@ #![feature(strict_provenance)] +use std::ffi::c_void; + +use jni::JavaVM; +use mem::RuntimeAllocator; + pub type Runtime = toad::std::Platform>; @@ -21,8 +26,22 @@ pub mod runtime; pub mod runtime_config; pub mod uint; +#[no_mangle] +pub extern "system" fn JNI_OnLoad(jvm: JavaVM, _: *const c_void) -> i32 { + toad_jni::global::init_with(jvm); + jni::sys::JNI_VERSION_1_8 +} + +#[no_mangle] +pub extern "system" fn JNI_OnUnload(_: JavaVM, _: *const c_void) { + unsafe {mem::Runtime::dealloc()} +} + +#[cfg(all(test, feature = "e2e"))] +pub mod e2e; + #[cfg(test)] -mod tests { +pub mod test { use std::sync::Once; use jni::{InitArgsBuilder, JavaVM}; @@ -33,20 +52,20 @@ mod tests { use crate::retry_strategy::RetryStrategy; use crate::runtime_config::RuntimeConfig; - static INIT: Once = Once::new(); pub fn init<'a>() -> java::Env<'a> { + static INIT: Once = Once::new(); INIT.call_once(|| { let jvm = - JavaVM::new(InitArgsBuilder::new().option("--enable-preview") - .option("-Djava.class.path=../target/scala-3.2.2/classes/") + JavaVM::new(InitArgsBuilder::new().option("-Djava.library.path=/home/orion/src/toad-lib/toad-java/target/glue/debug/") + .option("-Djava.class.path=../target/scala-3.2.2/classes") + .option("--enable-preview") .build() .unwrap()).unwrap(); toad_jni::global::init_with(jvm); }); toad_jni::global::jvm().attach_current_thread_permanently() - .unwrap(); - toad_jni::global::jvm().get_env().unwrap() + .unwrap() } #[test] diff --git a/glue/src/mem.rs b/glue/src/mem.rs index 6092332..39b3266 100644 --- a/glue/src/mem.rs +++ b/glue/src/mem.rs @@ -9,7 +9,10 @@ pub type Runtime = RuntimeGlobalStaticAllocator; /// strict provenance to prevent addresses from leaking outside of that memory region. pub trait RuntimeAllocator: core::default::Default + core::fmt::Debug + Copy { /// Allocate memory for the runtime and yield a stable pointer to it - unsafe fn alloc(r: crate::Runtime) -> *mut crate::Runtime; + unsafe fn alloc(r: impl FnOnce() -> crate::Runtime) -> *mut crate::Runtime; + + /// Teardown + unsafe fn dealloc() {} /// Coerce a `long` rep of the stable pointer created by [`Self::alloc`] to /// a pointer (preferably using strict_provenance) @@ -32,18 +35,19 @@ static mut RUNTIME: *mut crate::Runtime = core::ptr::null_mut(); pub struct RuntimeGlobalStaticAllocator; impl RuntimeAllocator for RuntimeGlobalStaticAllocator { /// Nops on already-init - unsafe fn alloc(r: crate::Runtime) -> *mut crate::Runtime { + unsafe fn alloc(r: impl FnOnce() -> crate::Runtime) -> *mut crate::Runtime { if RUNTIME.is_null() { - let p = - std::alloc::alloc(std::alloc::Layout::new::()).cast::(); - *p = r; - RUNTIME = p; + RUNTIME = Box::into_raw(Box::new(r())); RUNTIME } else { RUNTIME } } + unsafe fn dealloc() { + drop(Box::from_raw(RUNTIME)); + } + unsafe fn deref(_: i64) -> *mut crate::Runtime { RUNTIME } diff --git a/glue/src/runtime.rs b/glue/src/runtime.rs index 464f101..1cb7522 100644 --- a/glue/src/runtime.rs +++ b/glue/src/runtime.rs @@ -11,10 +11,14 @@ use crate::Runtime as ToadRuntime; pub struct Runtime(java::lang::Object); impl Runtime { - pub fn init(e: &mut java::Env, cfg: RuntimeConfig) -> i64 { - let r = - ToadRuntime::try_new(format!("0.0.0.0:{}", cfg.net(e).port(e)), cfg.to_toad(e)).unwrap(); - unsafe { crate::mem::Runtime::alloc(r).addr() as i64 } + pub fn get_or_init(e: &mut java::Env, cfg: RuntimeConfig) -> Self { + static GET_OR_INIT: java::StaticMethod Runtime> = java::StaticMethod::new("getOrInit"); + GET_OR_INIT.invoke(e, cfg) + } + + pub fn poll_req(&self, e: &mut java::Env) -> Option { + static POLL_REQ: java::Method java::util::Optional> = java::Method::new("pollReq"); + POLL_REQ.invoke(e, self).to_option(e) } pub fn addr(&self, e: &mut java::Env) -> i64 { @@ -25,6 +29,27 @@ impl Runtime { pub fn ref_(&self, e: &mut java::Env) -> &'static ToadRuntime { unsafe { crate::mem::Runtime::deref(self.addr(e)).as_ref().unwrap() } } + + fn init_impl(e: &mut java::Env, cfg: RuntimeConfig) -> i64 { + let r = || + ToadRuntime::try_new(format!("0.0.0.0:{}", cfg.net(e).port(e)), cfg.to_toad(e)).unwrap(); + unsafe { crate::mem::Runtime::alloc(r).addr() as i64 } + } + + fn poll_req_impl(&self, e: &mut java::Env) -> java::util::Optional { + match self.ref_(e).poll_req() { + | Ok(req) => { + let mr = MessageRef::new(e, req.data().msg()); + java::util::Optional::::of(e, mr) + }, + | Err(nb::Error::WouldBlock) => { + java::util::Optional::::empty(e) + }, + | Err(nb::Error::Other(err)) => { + e.throw(format!("{:?}", err)).unwrap(); + java::util::Optional::::empty(e) + }, + } } } java::object_newtype!(Runtime); @@ -41,29 +66,18 @@ pub extern "system" fn Java_dev_toad_Runtime_init<'local>(mut e: java::Env<'loca let e = &mut e; let cfg = java::lang::Object::from_local(e, cfg).upcast_to::(e); - Runtime::init(e, cfg) + Runtime::init_impl(e, cfg) } -// JNIEXPORT jobject JNICALL Java_dev_toad_Runtime_pollReq -// (JNIEnv *, jobject, jobject); #[no_mangle] pub extern "system" fn Java_dev_toad_Runtime_pollReq<'local>(mut e: java::Env<'local>, - runtime: JObject<'local>, - cfg: JObject<'local>) + runtime: JObject<'local>) -> jobject { let e = &mut e; - let runtime = java::lang::Object::from_local(e, runtime).upcast_to::(e); - match runtime.ref_(e).poll_req() { - | Ok(req) => { - let mr = MessageRef::new(e, req.data().msg()); - java::util::Optional::::of(e, mr).downcast(e) - .as_raw() - }, - | Err(nb::Error::WouldBlock) => java::util::Optional::::empty(e).downcast(e) - .as_raw(), - | Err(nb::Error::Other(err)) => { - e.throw(format!("{:?}", err)).unwrap(); - core::ptr::null_mut() - }, - } + java::lang::Object::from_local(e, runtime) + .upcast_to::(e) + .poll_req_impl(e) + .downcast(e) + .to_local(e) + .as_raw() } diff --git a/glue/src/runtime_config.rs b/glue/src/runtime_config.rs index 7a12ca9..3275b44 100644 --- a/glue/src/runtime_config.rs +++ b/glue/src/runtime_config.rs @@ -4,6 +4,7 @@ use toad::time::Millis; use toad_jni::java; use crate::retry_strategy::RetryStrategy; +use crate::uint; pub struct RuntimeConfig(java::lang::Object); @@ -74,15 +75,21 @@ impl java::Class for Net { const PATH: &'static str = concat!(package!(dev.toad.RuntimeOptions), "$Net"); } +static NET_PORT: java::Field = java::Field::new("port"); + impl Net { - pub fn port(&self, e: &mut java::Env) -> i16 { - static PORT: java::Method i16> = java::Method::new("port"); - PORT.invoke(e, self) + pub fn port(&self, e: &mut java::Env) -> u16 { + NET_PORT.get(e, self).to_rust(e) } - pub fn concurrency(&self, e: &mut java::Env) -> i16 { - static CONCURRENCY: java::Method i16> = java::Method::new("concurrency"); - CONCURRENCY.invoke(e, self) + pub fn set_port(&self, e: &mut java::Env, new: u16) { + let new = uint::u16::from_rust(e, new); + NET_PORT.set(e, self, new) + } + + pub fn concurrency(&self, e: &mut java::Env) -> u8 { + static CONCURRENCY: java::Field = java::Field::new("concurrency"); + CONCURRENCY.get(e, self).to_rust(e) } pub fn msg(&self, e: &mut java::Env) -> Msg { diff --git a/src/main/java/dev.toad/Runtime.java b/src/main/java/dev.toad/Runtime.java index d89158f..ecd3c5a 100644 --- a/src/main/java/dev.toad/Runtime.java +++ b/src/main/java/dev.toad/Runtime.java @@ -5,10 +5,18 @@ import java.util.Optional; public class Runtime { + static { + System.loadLibrary("toad_java_glue"); + } + private final long addr; private static native long init(RuntimeOptions o); - private native Optional pollReq(RuntimeOptions o); + private native Optional pollReq(); + + public static Runtime getOrInit(RuntimeOptions o) { + return new Runtime(o); + } public Runtime(RuntimeOptions o) { this.addr = Runtime.init(o);