-
Notifications
You must be signed in to change notification settings - Fork 8
fix: resolve unsafe concurrency in Android JNI layer #261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
fix: resolve unsafe concurrency in Android JNI layer #261
Conversation
jmanhype
commented
Nov 27, 2025
- Use LazyLock for safe static Mutex initialization instead of direct Mutex::new() which could have initialization races
- Handle mutex poisoning gracefully by recovering instead of panicking on .unwrap() calls
- Add double-start prevention to avoid spawning multiple server instances
- Store handle before blocking to allow stopServer to work during startup
- Release lock before blocking on readiness to avoid holding mutex during long waits
- Replace eprintln with tracing macros for consistent logging
- Add error codes for JNI callers to handle failures appropriately
- Document concurrency safety guarantees in module docs
Summary of ChangesHello @jmanhype, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the robustness and concurrency safety of the Android JNI layer for the Forge server. It addresses potential race conditions and stability issues by implementing safe static mutex initialization, graceful handling of mutex poisoning, and mechanisms to prevent multiple server instances. Additionally, it improves the control flow during server startup and shutdown, standardizes logging, and provides clearer error reporting to the Android application. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request significantly improves the concurrency safety of the Android JNI layer. The changes, such as using LazyLock for static initialization, handling mutex poisoning gracefully, preventing double server starts, and fixing a potential deadlock by releasing the mutex before blocking, are all excellent and make the code much more robust. The introduction of error codes for the JNI caller is also a great improvement for error handling on the client side. I have one minor suggestion to align the documentation with the implementation.
| /// # Returns | ||
| /// - Positive value: The port number the server is listening on | ||
| /// - `-1` (`ERR_ALREADY_RUNNING`): Server is already running | ||
| /// - `-2` (`ERR_LOCK_FAILED`): Failed to acquire lock (mutex poisoned) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation states that ERR_LOCK_FAILED (-2) is returned if the mutex is poisoned. However, the implementation recovers from a poisoned mutex by calling poisoned.into_inner() and continues execution, rather than returning an error. This makes the documentation inconsistent with the code's behavior. To avoid confusion for the JNI caller, this line should be removed from the documentation, especially since the ERR_LOCK_FAILED constant is marked as Reserved for future use.
- Use LazyLock for safe static Mutex initialization instead of direct Mutex::new() which could have initialization races - Handle mutex poisoning gracefully by recovering instead of panicking on .unwrap() calls - Add double-start prevention to avoid spawning multiple server instances - Store handle before blocking to allow stopServer to work during startup - Release lock before blocking on readiness to avoid holding mutex during long waits - Replace eprintln with tracing macros for consistent logging - Add error codes for JNI callers to handle failures appropriately - Document concurrency safety guarantees in module docs Co-authored-by: Automagik Genie 🧞 <genie@namastex.ai>
854f35f to
45076b6
Compare
|
@codex review this pr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| static RUNTIME: OnceLock<Runtime> = OnceLock::new(); | ||
| static SERVER_HANDLE: Mutex<Option<tokio::task::JoinHandle<()>>> = Mutex::new(None); | ||
| static LAST_ERROR: Mutex<Option<String>> = Mutex::new(None); | ||
| static SERVER_HANDLE: LazyLock<Mutex<Option<tokio::task::JoinHandle<()>>>> = | ||
| LazyLock::new(|| Mutex::new(None)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore JNI exports expected by Android app
android.rs now defines only the server handle and the start/stop JNI functions, removing the setDataDir/getLastError bindings that previously used LAST_ERROR. MainActivity.kt still declares those native methods (android/app/src/main/java/ai/namastex/forge/MainActivity.kt lines 23-27), so loading the library and invoking setDataDir during app startup will throw UnsatisfiedLinkError: Native method not found, preventing the Android app from running or reporting backend errors. The missing JNI exports need to be reinstated or the Kotlin declarations removed to avoid the crash.
Useful? React with 👍 / 👎.
|
@codex review this pr |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[cfg(feature = "android")] | ||
| #[unsafe(no_mangle)] | ||
| pub extern "C" fn Java_ai_namastex_forge_MainActivity_startServer( | ||
| _env: JNIEnv, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore removed JNI exports needed by MainActivity
The Android JNI module now only exports startServer/stopServer (lines 57-60), while the previous JNI functions setDataDir and getLastError were removed entirely. android/app/src/main/java/ai/namastex/forge/MainActivity.kt still declares and invokes external fun setDataDir(...) in onCreate and getLastError() in showServerError, so the native library will throw UnsatisfiedLinkError as soon as those methods are called and the app cannot start the server or surface errors.
Useful? React with 👍 / 👎.