diff --git a/java/.cargo/config.toml b/java/.cargo/config.toml index 24a6f21533..07cff72751 100644 --- a/java/.cargo/config.toml +++ b/java/.cargo/config.toml @@ -1,3 +1,3 @@ [env] -BABUSHKA_NAME = { value = "BabushkaPy", force = true } +BABUSHKA_NAME = { value = "BabushkaJava", force = true } BABUSHKA_VERSION = "0.1.0" diff --git a/java/Cargo.toml b/java/Cargo.toml index 01ecb23e4c..2cb1a29cb9 100644 --- a/java/Cargo.toml +++ b/java/Cargo.toml @@ -13,9 +13,15 @@ crate-type = ["cdylib"] redis = { path = "../submodules/redis-rs/redis", features = ["aio", "tokio-comp", "connection-manager", "tls", "tokio-rustls-comp"] } babushka = { path = "../babushka-core" } tokio = { version = "^1", features = ["rt", "macros", "rt-multi-thread", "time"] } -logger_core = {path = "../logger_core"} +logger_core = { path = "../logger_core" } tracing-subscriber = "0.3.16" +thiserror = "1.0.49" +num-derive = "0.4.1" +num-traits = "0.2.17" [profile.release] lto = true debug = true + +[build-dependencies] +cbindgen = "0.26.0" diff --git a/java/benchmarks/build.gradle b/java/benchmarks/build.gradle index 8d9e500284..8c0aaec33a 100644 --- a/java/benchmarks/build.gradle +++ b/java/benchmarks/build.gradle @@ -18,6 +18,8 @@ dependencies { implementation 'io.lettuce:lettuce-core:6.2.6.RELEASE' implementation 'commons-cli:commons-cli:1.5.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' + + implementation "net.java.dev.jna:jna:5.8.0" } // Apply a specific Java toolchain to ease working on different environments. diff --git a/java/benchmarks/src/main/java/javababushka/benchmarks/BenchmarkingApp.java b/java/benchmarks/src/main/java/javababushka/benchmarks/BenchmarkingApp.java index 66ea1f5029..e3316a1b71 100644 --- a/java/benchmarks/src/main/java/javababushka/benchmarks/BenchmarkingApp.java +++ b/java/benchmarks/src/main/java/javababushka/benchmarks/BenchmarkingApp.java @@ -11,6 +11,7 @@ import java.util.stream.Stream; import javababushka.benchmarks.jedis.JedisClient; import javababushka.benchmarks.jedis.JedisPseudoAsyncClient; +import javababushka.benchmarks.jna.Babushka; import javababushka.benchmarks.lettuce.LettuceAsyncClient; import javababushka.benchmarks.lettuce.LettuceClient; import org.apache.commons.cli.CommandLine; @@ -53,7 +54,7 @@ public static void main(String[] args) { testClientSetGet(LettuceAsyncClient::new, runConfiguration, true); break; case BABUSHKA: - System.out.println("Babushka not yet configured"); + testClientSetGet(Babushka::new, runConfiguration, false); break; } } @@ -150,7 +151,7 @@ private static RunConfiguration verifyOptions(CommandLine line) throws ParseExce case ALL_SYNC: return Stream.of( ClientName.JEDIS, - // ClientName.BABUSHKA, + ClientName.BABUSHKA, ClientName.LETTUCE); default: return Stream.of(e); @@ -227,8 +228,8 @@ public RunConfiguration() { concurrentTasks = List.of(10, 100); clients = new ClientName[] { - // ClientName.BABUSHKA, - ClientName.JEDIS, ClientName.JEDIS_ASYNC, ClientName.LETTUCE, ClientName.LETTUCE_ASYNC + ClientName.BABUSHKA, + ClientName.JEDIS, ClientName.LETTUCE, }; host = "localhost"; port = 6379; diff --git a/java/benchmarks/src/main/java/javababushka/benchmarks/jna/Babushka.java b/java/benchmarks/src/main/java/javababushka/benchmarks/jna/Babushka.java new file mode 100644 index 0000000000..9934a7ea8c --- /dev/null +++ b/java/benchmarks/src/main/java/javababushka/benchmarks/jna/Babushka.java @@ -0,0 +1,133 @@ +package javababushka.benchmarks.jna; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Structure; +import javababushka.benchmarks.SyncClient; +import javababushka.benchmarks.utils.ConnectionSettings; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +public class Babushka implements SyncClient { + + private final long ptr = lib.init_client0(42); + + // = enum + public static enum ResultType { + Str(0), + Int(1), + Nil(2), + Data(3), + Bulk(4), + Ok(5), + Err(6), + Undef(-1); + + private int id; + ResultType(int id) { + this.id = id; + } + + public static ResultType of(int val) { + switch (val) { + case 0: return ResultType.Str; + case 1: return ResultType.Int; + case 2: return ResultType.Nil; + case 3: return ResultType.Data; + case 4: return ResultType.Bulk; + case 5: return ResultType.Ok; + case 6: return ResultType.Err; + default: return ResultType.Undef; + } + } + } + + public interface RustLib extends Library { + + @Structure.FieldOrder({"error", "value_type", "string", "num"}) + public static class BabushkaResult extends Structure { + public BabushkaResult() { + error = null; + value_type = 0; + string = null; + num = 0; + } + public static class ByValue extends BabushkaResult implements Structure.ByValue { } + public String error = null; + public int value_type = 0; + public String string = null; + public long num = 0; + }; + + public long init_client0(int data); + public BabushkaResult.ByValue connect0(long client, String address); + public BabushkaResult.ByValue set0(long client, String key, String value); + public BabushkaResult.ByValue get0(long client, String key); + } + + private static final RustLib lib; + static { + var is_win = System.getProperty("os.name").contains("Windows"); + var targetDir = Paths.get("jna-stuff", "build", "resources", "main", is_win ? "win32-x86-64" : "linux-x86-64").toAbsolutePath(); + + //System.setProperty("jna.debug_load", "true"); + System.setProperty("jna.library.path", targetDir.toString()); + + var created = targetDir.toFile().mkdirs(); + try { + if (is_win) { + Files.copy( + Paths.get(System.getProperty("user.dir"), "target", "debug", "javababushka.dll"), + Paths.get(targetDir.toString(), "javababushka.dll"), + StandardCopyOption.REPLACE_EXISTING); + } else { + Files.copy( + Paths.get(System.getProperty("user.dir"), "..", "target", "debug", "libjavababushka.so"), + Paths.get(targetDir.toString(), "libjavababushka.so"), + StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException e) { + System.out.printf("Failed to copy lib: %s%n", e.getMessage()); + e.printStackTrace(); + } + + lib = Native.load("javababushka", RustLib.class); + } + + @Override + public void connectToRedis() { + connectToRedis(new ConnectionSettings("localhost", 6379, false)); + } + + @Override + public void connectToRedis(ConnectionSettings connectionSettings) { + var connStr = String.format( + "%s://%s:%d", + connectionSettings.useSsl ? "rediss" : "redis", + connectionSettings.host, + connectionSettings.port); + lib.connect0(ptr, connStr); + } + + @Override + public String getName() { + return "JNA babushka"; + } + + @Override + public void set(String key, String value) { + + } + + @Override + public String get(String key) { + var res = lib.get0(ptr, key); + if (res.value_type == ResultType.Str.id) { + return res.string; + } + return res.error; + } +} diff --git a/java/build.rs b/java/build.rs new file mode 100644 index 0000000000..6613129e4b --- /dev/null +++ b/java/build.rs @@ -0,0 +1,17 @@ +extern crate cbindgen; + +use std::env; +use cbindgen::Language; + +fn main() { + let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_language(Language::Cxx) + //.with_cpp_compat(true) + .generate() + .expect("Unable to generate bindings") + //.write_to_file("bindings.h"); + .write_to_file(format!("{}.hpp", env::var("CARGO_PKG_NAME").unwrap())); +} diff --git a/java/cbindgen.toml b/java/cbindgen.toml new file mode 100644 index 0000000000..078ed63e04 --- /dev/null +++ b/java/cbindgen.toml @@ -0,0 +1,672 @@ +# The language to output bindings in +# +# possible values: "C", "C++", "Cython" +# +# default: "C++" +language = "C++" + + + + +# Options for wrapping the contents of the header: + +# An optional string of text to output at the beginning of the generated file +# default: doesn't emit anything +header = "/* Text to put at the beginning of the generated file. Probably a license. */" + +# An optional string of text to output at the end of the generated file +# default: doesn't emit anything +trailer = "/* Text to put at the end of the generated file */" + +# An optional name to use as an include guard +# default: doesn't emit an include guard +include_guard = "BABUSHKA" + +# Whether to add a `#pragma once` guard +# default: doesn't emit a `#pragma once` +pragma_once = true + +# An optional string of text to output between major sections of the generated +# file as a warning against manual editing +# +# default: doesn't emit anything +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" + +# Whether to include a comment with the version of cbindgen used to generate the file +# default: false +include_version = true + +# An optional namespace to output around the generated bindings +# default: doesn't emit a namespace +namespace = "babushka" + +# An optional list of namespaces to output around the generated bindings +# default: [] +namespaces = [] + +# An optional list of namespaces to declare as using with "using namespace" +# default: [] +using_namespaces = [] + +# A list of sys headers to #include (with angle brackets) +# default: [] +sys_includes = ["string"] + +# A list of headers to #include (with quotes) +# default: [] +includes = [] + +# Whether cbindgen's default C/C++ standard imports should be suppressed. These +# imports are included by default because our generated headers tend to require +# them (e.g. for uint32_t). Currently, the generated imports are: +# +# * for C: , , , , +# +# * for C++: , , , , (depending on config) +# +# default: false +no_includes = false + +# Whether to make a C header C++ compatible. +# These will wrap generated functions into a `extern "C"` block, e.g. +# +# #ifdef __cplusplus +# extern "C" { +# #endif // __cplusplus +# +# // Generated functions. +# +# #ifdef __cplusplus +# } // extern "C" +# #endif // __cplusplus +# +# If the language is not C this option won't have any effect. +# +# default: false +cpp_compat = true + +# A list of lines to add verbatim after the includes block +after_includes = "#define VERSION 1" + + + +# Code Style Options + +# The style to use for curly braces +# +# possible values: "SameLine", "NextLine" +# +# default: "SameLine" +braces = "SameLine" + +# The desired length of a line to use when formatting lines +# default: 100 +line_length = 80 + +# The amount of spaces to indent by +# default: 2 +tab_width = 4 + +# Include doc comments from Rust as documentation +documentation = true + +# How the generated documentation should be commented. +# +# possible values: +# * "c": /* like this */ +# * "c99": // like this +# * "c++": /// like this +# * "doxy": like C, but with leading *'s on each line +# * "auto": "c++" if that's the language, "doxy" otherwise +# +# default: "auto" +documentation_style = "doxy" + +# How much of the documentation for each item is output. +# +# possible values: +# * "short": Only the first line. +# * "full": The full documentation. +# +# default: "full" +documentation_length = "short" + + + + +# Codegen Options + +# When generating a C header, the kind of declaration style to use for structs +# or enums. +# +# possible values: +# * "type": typedef struct { ... } MyType; +# * "tag": struct MyType { ... }; +# * "both": typedef struct MyType { ... } MyType; +# +# default: "both" +style = "both" + +# If this option is true `usize` and `isize` will be converted into `size_t` and `ptrdiff_t` +# instead of `uintptr_t` and `intptr_t` respectively. +usize_is_size_t = true + +# A list of substitutions for converting cfg's to ifdefs. cfgs which aren't +# defined here will just be discarded. +# +# e.g. +# `#[cfg(target = "freebsd")] ...` +# becomes +# `#if defined(DEFINE_FREEBSD) ... #endif` +[defines] + + + + +[export] +# A list of additional items to always include in the generated bindings if they're +# found but otherwise don't appear to be used by the public API. +# +# default: [] +include = [] + +# A list of items to not include in the generated bindings +# default: [] +exclude = [] + +# A prefix to add before the name of every item +# default: no prefix is added +prefix = "" + +# Types of items that we'll generate. If empty, then all types of item are emitted. +# +# possible items: (TODO: explain these in detail) +# * "constants": +# * "globals": +# * "enums": +# * "structs": +# * "unions": +# * "typedefs": +# * "opaque": +# * "functions": +# +# default: [] +item_types = ["enums", "structs", "opaque", "functions"] + +# Whether applying rules in export.rename prevents export.prefix from applying. +# +# e.g. given this toml: +# +# [export] +# prefix = "capi_" +# [export.rename] +# "MyType" = "my_cool_type" +# +# You get the following results: +# +# renaming_overrides_prefixing = true: +# "MyType" => "my_cool_type" +# +# renaming_overrides_prefixing = false: +# "MyType => capi_my_cool_type" +# +# default: false +renaming_overrides_prefixing = true + +# Table of name conversions to apply to item names (lhs becomes rhs) +[export.rename] + + +# Table of things to prepend to the body of any struct, union, or enum that has the +# given name. This can be used to add things like methods which don't change ABI, +# mark fields private, etc +[export.pre_body] + +# Table of things to append to the body of any struct, union, or enum that has the +# given name. This can be used to add things like methods which don't change ABI. +[export.body] + +# Configuration for name mangling +[export.mangle] +# Whether the types should be renamed during mangling, for example +# c_char -> CChar, etc. +rename_types = "PascalCase" +# Whether the underscores from the mangled name should be omitted. +remove_underscores = false + +[layout] +# A string that should come before the name of any type which has been marked +# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a +# reasonable value if targeting gcc/clang. A more portable solution would +# involve emitting the name of a macro which you define in a platform-specific +# way. e.g. "PACKED" +# +# default: `#[repr(packed)]` types will be treated as opaque, since it would +# be unsafe for C callers to use a incorrectly laid-out union. +packed = "PACKED" + +# A string that should come before the name of any type which has been marked +# as `#[repr(align(n))]`. This string must be a function-like macro which takes +# a single argument (the requested alignment, `n`). For instance, a macro +# `#define`d as `ALIGNED(n)` in `header` which translates to +# `__attribute__((aligned(n)))` would be a reasonable value if targeting +# gcc/clang. +# +# default: `#[repr(align(n))]` types will be treated as opaque, since it +# could be unsafe for C callers to use a incorrectly-aligned union. +aligned_n = "ALIGNED" + + +[fn] +# An optional prefix to put before every function declaration +# default: no prefix added +prefix = "" + +# An optional postfix to put after any function declaration +# default: no postix added +postfix = "" + +# How to format function arguments +# +# possible values: +# * "horizontal": place all arguments on the same line +# * "vertical": place each argument on its own line +# * "auto": only use vertical if horizontal would exceed line_length +# +# default: "auto" +args = "horizontal" + +# An optional string that should prefix function declarations which have been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused_result))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_FUNC" +# default: nothing is emitted for must_use functions +must_use = "" + +# An optional string that should prefix function declarations which have been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_FUNC" +# default: nothing is emitted for deprecated functions +deprecated = "DEPRECATED_FUNC" + +# An optional string that should prefix function declarations which have been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_FUNC_WITH_NOTE(note)" +# default: nothing is emitted for deprecated functions +deprecated_with_note = "DEPRECATED_FUNC_WITH_NOTE" + +# An optional string that will be used in the attribute position for functions +# that don't return (that return `!` in Rust). +# +# For instance, `__attribute__((noreturn))` would be a reasonable value if +# targeting gcc/clang. +no_return = "NO_RETURN" + +# An optional string that, if present, will be used to generate Swift function +# and method signatures for generated functions, for example "CF_SWIFT_NAME". +# If no such macro is available in your toolchain, you can define one using the +# `header` option in cbindgen.toml +# default: no swift_name function attributes are generated +swift_name_macro = "" + +# A rule to use to rename function argument names. The renaming assumes the input +# is the Rust standard snake_case, however it accepts all the different rename_args +# inputs. This means many options here are no-ops or redundant. +# +# possible values (that actually do something): +# * "CamelCase": my_arg => myArg +# * "PascalCase": my_arg => MyArg +# * "GeckoCase": my_arg => aMyArg +# * "ScreamingSnakeCase": my_arg => MY_ARG +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose here): +# * "SnakeCase": apply no renaming +# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?) +# * "UpperCase": same as ScreamingSnakeCase in this context +# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context +# +# default: "None" +rename_args = "PascalCase" + +# This rule specifies the order in which functions will be sorted. +# +# "Name": sort by the name of the function +# "None": keep order in which the functions have been parsed +# +# default: "None" +sort_by = "Name" + +[struct] +# A rule to use to rename struct field names. The renaming assumes the input is +# the Rust standard snake_case, however it acccepts all the different rename_args +# inputs. This means many options here are no-ops or redundant. +# +# possible values (that actually do something): +# * "CamelCase": my_arg => myArg +# * "PascalCase": my_arg => MyArg +# * "GeckoCase": my_arg => mMyArg +# * "ScreamingSnakeCase": my_arg => MY_ARG +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose here): +# * "SnakeCase": apply no renaming +# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?) +# * "UpperCase": same as ScreamingSnakeCase in this context +# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context +# +# default: "None" +rename_fields = "PascalCase" + +# An optional string that should come before the name of any struct which has been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_STRUCT" +# +# default: nothing is emitted for must_use structs +must_use = "" + +# An optional string that should come before the name of any struct which has been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_STRUCT" +# default: nothing is emitted for deprecated structs +deprecated = "DEPRECATED_STRUCT" + +# An optional string that should come before the name of any struct which has been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_STRUCT_WITH_NOTE(note)" +# default: nothing is emitted for deprecated structs +deprecated_with_note = "DEPRECATED_STRUCT_WITH_NOTE" + +# Whether a Rust type with associated consts should emit those consts inside the +# type's body. Otherwise they will be emitted trailing and with the type's name +# prefixed. This does nothing if the target is C, or if +# [const]allow_static_const = false +# +# default: false +# associated_constants_in_body: false + +# Whether to derive a simple constructor that takes a value for every field. +# default: false +derive_constructor = true + +# Whether to derive an operator== for all structs +# default: false +derive_eq = false + +# Whether to derive an operator!= for all structs +# default: false +derive_neq = false + +# Whether to derive an operator< for all structs +# default: false +derive_lt = false + +# Whether to derive an operator<= for all structs +# default: false +derive_lte = false + +# Whether to derive an operator> for all structs +# default: false +derive_gt = false + +# Whether to derive an operator>= for all structs +# default: false +derive_gte = false + + + + + +[enum] +# A rule to use to rename enum variants, and the names of any fields those +# variants have. This should probably be split up into two separate options, but +# for now, they're the same! See the documentation for `[struct]rename_fields` +# for how this applies to fields. Renaming of the variant assumes that the input +# is the Rust standard PascalCase. In the case of QualifiedScreamingSnakeCase, +# it also assumed that the enum's name is PascalCase. +# +# possible values (that actually do something): +# * "CamelCase": MyVariant => myVariant +# * "SnakeCase": MyVariant => my_variant +# * "ScreamingSnakeCase": MyVariant => MY_VARIANT +# * "QualifiedScreamingSnakeCase": MyVariant => ENUM_NAME_MY_VARIANT +# * "LowerCase": MyVariant => myvariant +# * "UpperCase": MyVariant => MYVARIANT +# * "None": apply no renaming +# +# technically possible values (that shouldn't have a purpose for the variants): +# * "PascalCase": apply no renaming +# * "GeckoCase": apply no renaming +# +# default: "None" +rename_variants = "None" + +# Whether an extra "sentinel" enum variant should be added to all generated enums. +# Firefox uses this for their IPC serialization library. +# +# WARNING: if the sentinel is ever passed into Rust, behaviour will be Undefined. +# Rust does not know about this value, and will assume it cannot happen. +# +# default: false +add_sentinel = false + +# Whether enum variant names should be prefixed with the name of the enum. +# default: false +prefix_with_name = false + +# Whether to emit enums using "enum class" when targeting C++. +# default: true +enum_class = true + +# Whether to generate static `::MyVariant(..)` constructors and `bool IsMyVariant()` +# methods for enums with fields. +# +# default: false +derive_helper_methods = false + +# Whether to generate `const MyVariant& AsMyVariant() const` methods for enums with fields. +# default: false +derive_const_casts = false + +# Whether to generate `MyVariant& AsMyVariant()` methods for enums with fields +# default: false +derive_mut_casts = false + +# The name of the macro/function to use for asserting `IsMyVariant()` in the body of +# derived `AsMyVariant()` cast methods. +# +# default: "assert" (but also causes `` to be included by default) +cast_assert_name = "" + +# An optional string that should come before the name of any enum which has been +# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "MUST_USE_ENUM" +# +# Note that this refers to the *output* type. That means this will not apply to an enum +# with fields, as it will be emitted as a struct. `[struct]must_use` will apply there. +# +# default: nothing is emitted for must_use enums +must_use = "" + +# An optional string that should come before the name of any enum which has been +# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_ENUM" +# default: nothing is emitted for deprecated enums +deprecated = "DEPRECATED_ENUM" + +# An optional string that should come before the name of any enum which has been +# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the +# double-quoted string. For instance, "__attribute__((deprecated({})))" +# would be a reasonable value if targeting gcc/clang. A more portable solution +# would involve emitting the name of a macro which you define in a +# platform-specific way. e.g. "DEPRECATED_ENUM_WITH_NOTE(note)" +# default: nothing is emitted for deprecated enums +deprecated_with_note = "DEPRECATED_ENUM_WITH_NOTE" + +# Whether enums with fields should generate destructors. This exists so that generic +# enums can be properly instantiated with payloads that are C++ types with +# destructors. This isn't necessary for structs because C++ has rules to +# automatically derive the correct constructors and destructors for those types. +# +# Care should be taken with this option, as Rust and C++ cannot +# properly interoperate with eachother's notions of destructors. Also, this may +# change the ABI for the type. Either your destructor-full enums must live +# exclusively within C++, or they must only be passed by-reference between +# C++ and Rust. +# +# default: false +derive_tagged_enum_destructor = false + +# Whether enums with fields should generate copy-constructor. See the discussion on +# derive_tagged_enum_destructor for why this is both useful and very dangerous. +# +# default: false +derive_tagged_enum_copy_constructor = false +# Whether enums with fields should generate copy-assignment operators. +# +# This depends on also deriving copy-constructors, and it is highly encouraged +# for this to be set to true. +# +# default: false +derive_tagged_enum_copy_assignment = false + +# Whether enums with fields should generate an empty, private destructor. +# This allows the auto-generated constructor functions to compile, if there are +# non-trivially constructible members. This falls in the same family of +# dangerousness as `derive_tagged_enum_copy_constructor` and co. +# +# default: false +private_default_tagged_enum_constructor = false + + + + + +[const] +# Whether a generated constant can be a static const in C++ mode. I have no +# idea why you would turn this off. +# +# default: true +allow_static_const = true + +# Whether a generated constant can be constexpr in C++ mode. +# +# default: true +allow_constexpr = false + +# This rule specifies the order in which constants will be sorted. +# +# "Name": sort by the name of the constant +# "None": keep order in which the constants have been parsed +# +# default: "None" +sort_by = "Name" + + + + +[macro_expansion] +# Whether bindings should be generated for instances of the bitflags! macro. +# default: false +bitflags = true + + + + + + +# Options for how your Rust library should be parsed + +[parse] +# Whether to parse dependent crates and include their types in the output +# default: false +parse_deps = true + +# A white list of crate names that are allowed to be parsed. If this is defined, +# only crates found in this list will ever be parsed. +# +# default: there is no whitelist (NOTE: this is the opposite of []) +include = ["redis"] + +# A black list of crate names that are not allowed to be parsed. +# default: [] +exclude = [] + +# Whether to use a new temporary target directory when running `rustc -Zunpretty=expanded`. +# This may be required for some build processes. +# +# default: false +clean = false + +# Which crates other than the top-level binding crate we should generate +# bindings for. +# +# default: [] +extra_bindings = [] + +[parse.expand] +# A list of crate names that should be run through `cargo expand` before +# parsing to expand any macros. Note that if a crate is named here, it +# will always be parsed, even if the blacklist/whitelist says it shouldn't be. +# +# default: [] +crates = [] + +# If enabled, use the `--all-features` option when expanding. Ignored when +# `features` is set. For backwards-compatibility, this is forced on if +# `expand = ["euclid"]` shorthand is used. +# +# default: false +all_features = false + +# When `all_features` is disabled and this is also disabled, use the +# `--no-default-features` option when expanding. +# +# default: true +default_features = true + +# A list of feature names that should be used when running `cargo expand`. This +# combines with `default_features` like in your `Cargo.toml`. Note that the features +# listed here are features for the current crate being built, *not* the crates +# being expanded. The crate's `Cargo.toml` must take care of enabling the +# appropriate features in its dependencies +# +# default: [] +features = ["cbindgen", "aio", "tokio-comp", "connection-manager", "tls", "tokio-rustls-comp", "rt", "macros", "rt-multi-thread", "time"] + +[ptr] +# An optional string to decorate all pointers that are +# required to be non null. Nullability is inferred from the Rust type: `&T`, +# `&mut T` and `NonNull` all require a valid pointer value. +non_null_attribute = "" + +# Options specific to Cython bindings. + +[cython] + +# Header specified in the top level `cdef extern from header:` declaration. +# +# default: * +header = '*' + +# `from module cimport name1, name2` declarations added in the same place +# where you'd get includes in C. +[cython.cimports] +module = [] diff --git a/java/javababushka.hpp b/java/javababushka.hpp new file mode 100644 index 0000000000..37a90b06a2 --- /dev/null +++ b/java/javababushka.hpp @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include + +template +struct Option; + +struct BabushkaResultStr { + const char *error; + const char *result; +}; + +struct BabushkaResult { + const char *error; + uint32_t value_type; + const char *str; + int64_t num; +}; + +struct BabushkaClient { + Option> runtime; + Option> connection; + int32_t data; +}; + +extern "C" { + +BabushkaResultStr static_function_which_throws(); + +BabushkaResultStr static_function4(); + +BabushkaResult static_function2_0(); + +BabushkaResult static_function2_1(); + +BabushkaResult static_function2_2(); + +BabushkaResult static_function2_3(); + +BabushkaResult static_function2_4(); + +uint64_t init_client0(int32_t data); + +BabushkaResult test0(uint64_t ptr, const char *address); + +BabushkaResult connect0(uint64_t ptr, const char *address); + +BabushkaResult set0(uint64_t ptr, const char *key, const char *value); + +BabushkaResult get0(uint64_t ptr, const char *key); + +BabushkaResult set(BabushkaClient *self, const char *key, const char *value); + +BabushkaResult get(BabushkaClient *self, const char *key); + +} // extern "C" diff --git a/java/jna-stuff/build.gradle b/java/jna-stuff/build.gradle new file mode 100644 index 0000000000..941771480f --- /dev/null +++ b/java/jna-stuff/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'java' + id 'application' +} + +group = 'org.example' +version = '1.13.0' + +repositories { + mavenCentral() +} + +dependencies { + // Required for interop + implementation "net.java.dev.jna:jna:5.8.0" +} + +test { + useJUnitPlatform() +} + +application { + mainClass = 'Loader' +} diff --git a/java/jna-stuff/src/main/java/Loader.java b/java/jna-stuff/src/main/java/Loader.java new file mode 100644 index 0000000000..07a8bf8965 --- /dev/null +++ b/java/jna-stuff/src/main/java/Loader.java @@ -0,0 +1,180 @@ +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Structure; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.List; + +public class Loader { + // = enum + public static enum ResultType { + Str(0), + Int(1), + Nil(2), + Data(3), + Bulk(4), + Ok(5), + Err(6), + Undef(-1); + + private int id; + ResultType(int id) { + this.id = id; + } + + public static ResultType of(int val) { + switch (val) { + case 0: return ResultType.Str; + case 1: return ResultType.Int; + case 2: return ResultType.Nil; + case 3: return ResultType.Data; + case 4: return ResultType.Bulk; + case 5: return ResultType.Ok; + case 6: return ResultType.Err; + default: return ResultType.Undef; + } + } + }; + + public interface RustLib extends Library { + + public BabushkaResultStr.ByValue static_function_which_throws(); + public BabushkaResultStr.ByValue static_function4(); + + //public BabushkaResult.ByValue static_function2_0(); + public BabushkaResult.ByValue static_function2_0(); + public BabushkaResult.ByValue static_function2_1(); + public BabushkaResult.ByValue static_function2_2(); + public BabushkaResult.ByValue static_function2_3(); + public BabushkaResult.ByValue static_function2_4(); + + + @Structure.FieldOrder({"error", "result"}) + public static class BabushkaResultStr extends Structure { + public static class ByValue extends BabushkaResultStr implements Structure.ByValue { } + public String error; + public String result; + } + + @Structure.FieldOrder({"error", "value_type", "string", "num"}) + public static class BabushkaResult extends Structure { + public BabushkaResult() { + error = null; + value_type = 0; + string = null; + num = 0; + } + public static class ByValue extends BabushkaResult implements Structure.ByValue { } + public String error = null; + public int value_type = 0; + //public BabushkaValue.ByReference value; + public String string = null; + public long num = 0; + }; + + public static class BabushkaClient extends Structure { + public static class ByValue extends BabushkaClient implements Structure.ByValue { } + public static class ByReference extends BabushkaClient implements Structure.ByReference { } + } + + public long init_client0(int data); + public BabushkaResult.ByValue connect0(long client, String address); + public BabushkaResult.ByValue test0(long client, String address); + + + public BabushkaResult.ByValue set0(long client, String key, String value); + + public BabushkaResult.ByValue get0(long client, String key); + + + } + + public static void main(String [] args) { + var is_win = System.getProperty("os.name").contains("Windows"); + var targetDir = Paths.get("jna-stuff", "build", "resources", "main", is_win ? "win32-x86-64" : "linux-x86-64").toAbsolutePath(); + + //System.setProperty("jna.debug_load", "true"); + System.setProperty("jna.library.path", targetDir.toString()); + + var created = targetDir.toFile().mkdirs(); + try { + if (is_win) { + Files.copy( + Paths.get(System.getProperty("user.dir"), "target", "debug", "javababushka.dll"), + Paths.get(targetDir.toString(), "javababushka.dll"), + StandardCopyOption.REPLACE_EXISTING); + } else { + Files.copy( + Paths.get(System.getProperty("user.dir"), "..", "target", "debug", "libjavababushka.so"), + Paths.get(targetDir.toString(), "libjavababushka.so"), + StandardCopyOption.REPLACE_EXISTING); + } + } catch (IOException e) { + System.out.printf("Failed to copy lib: %s%n", e.getMessage()); + e.printStackTrace(); + } + + + //System.out.println("Working Directory = " + System.getProperty("jna.library.path")); + + var lib = (RustLib) Native.load( + "javababushka", + RustLib.class); + var res = lib.static_function_which_throws(); + + var res4 = lib.static_function4(); + + var res2_0 = lib.static_function2_0(); + var res2_1 = lib.static_function2_1(); + var res2_2 = lib.static_function2_2(); + var res2_3 = lib.static_function2_3(); + var res2_4 = lib.static_function2_4(); + int a = 5; + + var bab = lib.init_client0(42); + var t1 = lib.test0(bab, "pewpew"); + var t2 = lib.test0(bab, "ololo"); + var t3 = lib.test0(bab, "ikiki"); + + + + + + + + + + + System.out.println("Before connect"); + var con = lib.connect0(bab, "redis://127.0.0.1:6379"); + System.out.println("After connect"); + if (con.value_type == ResultType.Ok.id) { + System.out.println("Connected"); + } else { + System.out.printf("Conn failed: Res = %s, str = %s, err = %s%n", ResultType.of(con.value_type), con.string, con.error); + } + + System.out.println("=======\nBefore set"); + var set = lib.set0(bab, "kkkey", "ololo"); + System.out.println("After set"); + if (set.value_type == ResultType.Ok.id) { + System.out.println("Set ok"); + } else { + System.out.printf("Set failed: Res = %s, str = %s, err = %s%n", ResultType.of(set.value_type), set.string, set.error); + } + + System.out.println("=======\nBefore get"); + var get = lib.get0(bab, "key"); + System.out.println("After get"); + if (get.value_type == ResultType.Str.id) { + System.out.printf("Get ok: %s%n", get.string); + } else { + System.out.printf("Set failed: Res = %s, str = %s, err = %s%n", ResultType.of(get.value_type), get.string, get.error); + } + + int b = 5; + } +} diff --git a/java/settings.gradle b/java/settings.gradle index c392c65247..1b66925d56 100644 --- a/java/settings.gradle +++ b/java/settings.gradle @@ -3,3 +3,4 @@ rootProject.name = 'javababushka' include 'client' include 'integTest' include 'benchmarks' +include 'jna-stuff' diff --git a/java/src/lib.rs b/java/src/lib.rs index e69de29bb2..7b5f5249c0 100644 --- a/java/src/lib.rs +++ b/java/src/lib.rs @@ -0,0 +1,493 @@ +use std::os::raw::c_char; +use std::ffi::{CStr,CString}; +use std::str; +use std::mem; +use std::ptr::null; +use std::sync::{Mutex}; +//* +use redis::aio::MultiplexedConnection; +use redis::{Client, ErrorKind, FromRedisValue, RedisResult, Value}; +use redis::{AsyncCommands, RedisError}; +use tokio::runtime::Builder; +use tokio::runtime::Runtime; +// */ + + +#[no_mangle] +pub extern fn static_function_which_throws() -> BabushkaResultStr { + BabushkaResultStr { error: str_to_ptr(""), result: str_to_ptr("hello from rust -> static function which throws") } +} + +#[no_mangle] +pub extern fn static_function4() -> BabushkaResultStr { + BabushkaResultStr { error: str_to_ptr("pewpew"), result: null() } +} + +#[no_mangle] +pub extern fn static_function2_0() -> BabushkaResult { + //BabushkaResult::from_str(str_to_ptr("ololo")) + BabushkaResult { + error: null(), + value_type: ResultType::Str as u32, + str: str_to_ptr("ololo"), + num: 0 + } +} + +#[no_mangle] +pub extern fn static_function2_1() -> BabushkaResult { + //BabushkaResult::from_err(str_to_ptr("oh no")) + BabushkaResult { + error: str_to_ptr("oh no"), + value_type: ResultType::Err as u32, + str: null(), + num: 0 + } +} + +#[no_mangle] +pub extern fn static_function2_2() -> BabushkaResult { + //BabushkaResult::from_int(100500) + BabushkaResult { + error: null(), + value_type: ResultType::Int as u32, + str: null(), + num: 100500 + } +} + +#[no_mangle] +pub extern fn static_function2_3() -> BabushkaResult { + //BabushkaResult::from_ok() + BabushkaResult { + error: null(), + value_type: ResultType::Ok as u32, + str: null(), + num: 0 + } +} + +#[no_mangle] +pub extern fn static_function2_4() -> BabushkaResult { + BabushkaResult { + error: null(), + value_type: ResultType::Nil as u32, + str: null(), + num: 0 + } +} + +/// Convert a native string to a Rust string +fn ptr_to_string(pointer: * const c_char) -> String { + let slice = unsafe { CStr::from_ptr(pointer).to_bytes() }; + str::from_utf8(slice).unwrap().to_string() +} + +/// Convert a native string to a Rust string +fn ptr_to_str(pointer: * const c_char) -> &'static str { + let slice = unsafe { CStr::from_ptr(pointer).to_bytes() }; + str::from_utf8(slice).unwrap() +} + +/// Convert a Rust string to a native string +fn string_to_ptr(string: String) -> * const c_char { + let cs = CString::new(string.as_bytes()).unwrap(); + let ptr = cs.as_ptr(); + // Tell Rust not to clean up the string while we still have a pointer to it. + // Otherwise, we'll get a segfault. + mem::forget(cs); + ptr +} + +/// Convert a Rust string to a native string +fn str_to_ptr(string: &str) -> * const c_char { + let cs = CString::new(string.as_bytes()).unwrap(); + let ptr = cs.as_ptr(); + // Tell Rust not to clean up the string while we still have a pointer to it. + // Otherwise, we'll get a segfault. + mem::forget(cs); + ptr +} + +#[repr(C)] +pub struct BabushkaResultStr { + pub error: * const c_char, + pub result: * const c_char +} + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; + +// redis::Value +#[derive(FromPrimitive)] +pub enum ResultType { + Str = 0, // * const c_char + Int = 1, // i64 + Nil = 2, + Data = 3, // Vec + Bulk = 4, // Vec + Ok = 5, + Err = 6 +} + +#[repr(C)] +pub struct BabushkaResult { + pub error: * const c_char, + pub value_type: u32, //ResultType, + pub str: * const c_char, + pub num: i64, +} + +impl BabushkaResult { + pub fn from_str(str: String) -> Self { + Self { + error: null(), + value_type: ResultType::Str as u32, + str: string_to_ptr(str), + num: 0, + } + } + + pub fn from_empty_str() -> Self { + Self { + error: null(), + value_type: ResultType::Str as u32, + str: null(), + num: 0, + } + } + + pub fn from_int(int: i64) -> Self { + Self { + error: null(), + value_type: ResultType::Int as u32, + str: null(), + num: int, + } + } + + pub fn from_err(err: String) -> Self { + Self { + error: string_to_ptr(err), + value_type: ResultType::Err as u32, + str: null(), + num: 0, + } + } + + pub fn from_nil() -> Self { + Self { + error: null(), + value_type: ResultType::Nil as u32, + str: null(), + num: 0, + } + } + + pub fn from_ok() -> Self { + Self { + error: null(), + value_type: ResultType::Ok as u32, + str: null(), + num: 0, + } + } + + pub fn get_type(&self) -> ResultType { + // unsafe { std::mem::transmute(self.value_type as u32) }; + ResultType::from_u32(self.value_type).unwrap() + } + + pub fn get_err(&self) -> String { + ptr_to_string(self.error) + } + + pub fn get_str(&self) -> String { + ptr_to_string(self.str) + } + + pub fn get_int(&self) -> i64 { + self.num + } + // TODO other types +} + +#[repr(C)] +pub struct BabushkaValue { + str: * const c_char, + num: i64, +// data: Vec, +// bulk: Vec, +} + +impl Default for BabushkaValue { + fn default() -> Self { + Self { + str: null(), + num: 0, +// data: Vec::default(), +// bulk: Vec::default() + } + } +} + + + +#[repr(C)] +pub struct BabushkaClient { + runtime: Option>, + connection: Option>, + //runtime: Option>, + //connection: Option>, + data: i32 +} + + +#[no_mangle] +pub extern fn init_client0(data: i32) -> u64 { + let p = Box::::into_raw(Box::new( + BabushkaClient { + runtime: None, + connection: None, + data + })); + p as u64 + //p.into(); +} + +#[no_mangle] +pub extern fn test0(ptr: u64, address: * const c_char) -> BabushkaResult { + let mut babushka = unsafe { Box::from_raw(ptr as *mut BabushkaClient) }; + babushka.data += 15; + BabushkaResult { + error: null(), + value_type: ResultType::Ok as u32, + str: string_to_ptr(format!("{} {}", babushka.data, ptr_to_string(address))), + num: Box::::into_raw(babushka) as i64, + } +} + +//* +#[no_mangle] +pub extern fn connect0(ptr: u64, address: * const c_char) -> BabushkaResult { + let mut babushka = unsafe { Box::from_raw(ptr as *mut BabushkaClient) }; + let client_res = redis::Client::open(ptr_to_string(address)); + let client : Client; + + match client_res { + Ok(c) => client = c, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + let runtime_res = + Builder::new_multi_thread() + .enable_all() + .thread_name("Babushka java thread") + .build(); + + let runtime : Runtime; + + match runtime_res { + Ok(rt) => runtime = rt, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + let _runtime_handle = runtime.enter(); + + let connection_res = runtime + .block_on(client.get_multiplexed_async_connection()); + + let connection : MultiplexedConnection; + + match connection_res { + Ok(c) => connection = c, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + babushka.runtime = Some(Mutex::new(runtime)); + babushka.connection = Some(Mutex::new(connection)); + + Box::::into_raw(babushka); + BabushkaResult::from_ok() +} + +#[no_mangle] +pub extern fn set0(ptr: u64, key: * const c_char, value: * const c_char) -> BabushkaResult { + let mut babushka = unsafe { Box::from_raw(ptr as *mut BabushkaClient) }; + //self.runtime.spawn(async move { + babushka.connection.as_mut().unwrap().lock().unwrap() + .set::(ptr_to_string(key), ptr_to_string(value)); // TODO try RedisValue + //}); + Box::::into_raw(babushka); + BabushkaResult::from_ok() +} + +#[no_mangle] +pub extern fn get0(ptr: u64, key: * const c_char) -> BabushkaResult { + let mut babushka = unsafe { Box::from_raw(ptr as *mut BabushkaClient) }; + let res = babushka.runtime.as_mut().unwrap().lock().unwrap().block_on(async { + babushka.connection.as_mut().unwrap().lock().unwrap().get::>(ptr_to_string(key)).await + }); + Box::::into_raw(babushka); + match res { + Ok(Some(val)) => BabushkaResult::from_str(val), + Ok(None) => BabushkaResult::from_empty_str(), + Err(err) => BabushkaResult::from_err(err.to_string()) + } +} +// */ + +impl BabushkaClient { + /* + fn new() -> Self { + Self { + runtime: None, + connection: None + } + }*/ + +/* + #[no_mangle] + pub extern fn connect(&mut self, address: * const c_char) -> BabushkaResult { + self.runtime = Some(Mutex::new(BabushkaValue::default())); + self.connection = Some(Mutex::new(BabushkaValue::default())); + + BabushkaResult::from_str(ptr_to_string(address).to_lowercase()) + } +*/ + #[no_mangle] + pub extern fn set(&mut self, key: * const c_char, value: * const c_char) -> BabushkaResult { + BabushkaResult::from_str(ptr_to_string(key) + ptr_to_str(value)) + } + + #[no_mangle] + pub extern fn get(&mut self, key: * const c_char) -> BabushkaResult { + BabushkaResult::from_str(ptr_to_string(key).repeat(3)) + } + +/* + #[no_mangle] + pub extern fn connect(&mut self, address: String) -> BabushkaResult { + //self.client = Some(redis::Client::open(address)?); + let client_res = redis::Client::open(address); + let client : Client; + + match client_res { + Ok(c) => client = c, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + let runtime_res = + Builder::new_multi_thread() + .enable_all() + .thread_name("Babushka java thread") + .build(); + + let runtime : Runtime; + + match runtime_res { + Ok(rt) => runtime = rt, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + let _runtime_handle = runtime.enter(); + + let connection_res = runtime + .block_on(client.get_multiplexed_async_connection()); + + let connection : MultiplexedConnection; + + match connection_res { + Ok(c) => connection = c, + Err(err) => return BabushkaResult::from_err(err.to_string()) + } + + self.runtime = Some(Mutex::new(runtime)); + self.connection = Some(Mutex::new(connection)); + + BabushkaResult::from_ok() + } + + /* + pub fn disconnect(&mut self) -> Result<(), RedisError> { + + } + */ + // TODO support any type for value + #[no_mangle] + pub extern fn set(&mut self, key: String, value: String) -> BabushkaResult { + //self.runtime.spawn(async move { + self.connection.as_mut().unwrap().lock().unwrap() + //.set/*::*/(key, value); + .set::(key, value); // TODO try RedisValue + //}); + BabushkaResult::from_ok() + } + + #[no_mangle] + pub extern fn set2(&mut self, key: String, value: Option) -> BabushkaResult { + //self.runtime.spawn(async move { + //let val: Value = value.map_or_else(|| { Value::Nil }, |s| { Value::Status(s) }); + self.connection.as_mut().unwrap().lock().unwrap() + //.set/*::*/(key, value); + .set::, Option, ()>(Some(key), value); // TODO try RedisValue + //}); + BabushkaResult::from_ok() + } + + // TODO support any type for value + // TODO support null value (Option<...>) + // TODO handle other types + #[no_mangle] + pub extern fn get(&mut self, key: String) -> BabushkaResult { + let res = self.runtime.as_mut().unwrap().lock().unwrap().block_on(async { + self.connection.as_mut().unwrap().lock().unwrap().get::(key).await + }); + match res { + Ok(val) => BabushkaResult::from_str(val), + Err(err) => BabushkaResult::from_err(err.to_string()) + } + } + + #[no_mangle] + pub extern fn get2(&mut self, key: String) -> BabushkaResult { + let res = self.runtime.as_mut().unwrap().lock().unwrap().block_on(async { + self.connection.as_mut().unwrap().lock().unwrap().get::(key).await + }); + match res { + Ok(val) => BabushkaResult::from_str(String::from_redis_value(&val).unwrap()), + Err(err) => BabushkaResult::from_err(err.to_string()) + } + } + + #[no_mangle] + pub extern fn get3(&mut self, key: String) -> BabushkaResult { + let res = self.runtime.as_mut().unwrap().lock().unwrap().block_on(async { + self.connection.as_mut().unwrap().lock().unwrap().get::>(key).await + }); + match res { + Ok(Some(val)) => BabushkaResult::from_str(val), + Ok(None) => BabushkaResult::from_empty_str(), + Err(err) => BabushkaResult::from_err(err.to_string()) + } + } + */ + + /* + #[no_mangle] + pub extern fn get4(&mut self, key: String) -> Result<* const String, String> { + let res = self.runtime.as_mut().unwrap().lock().unwrap().block_on(async { + self.connection.as_mut().unwrap().lock().unwrap().get::>(key).await + }); + match res { + Ok(val) => { + let value: Option<* const String> = val.map(|val| { + &val as *const String + }); + Ok(value.unwrap_or(std::ptr::null())) + }, + Err(err) => Err(err.to_string()) + } + } + */ +}