Skip to content

fix: preserve xconnector waits across suspend interrupts - #1033

Merged
utkarshdalal merged 3 commits into
utkarshdalal:masterfrom
xXJSONDeruloXx:fix/suspend-epoll-eintr
Mar 28, 2026
Merged

fix: preserve xconnector waits across suspend interrupts#1033
utkarshdalal merged 3 commits into
utkarshdalal:masterfrom
xXJSONDeruloXx:fix/suspend-epoll-eintr

Conversation

@xXJSONDeruloXx

@xXJSONDeruloXx xXJSONDeruloXx commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

https://discord.com/channels/1378308569287622737/1412756778159964201/1486379950238994554


Summary by cubic

Make xconnector epoll/poll waits resilient to EINTR so connections survive device suspend without stalls or drops. Adds a native xconnectorpatch bridge and routes Java calls through it for reliable waiting and FD handling.

  • Bug Fixes

    • Retry epoll_wait, poll, and accept on EINTR; log failures and ignore transient EAGAIN/EWOULDBLOCK.
    • Treat POLLERR/POLLHUP/POLLNVAL as closed; ensure waitForSocketRead exits on shutdown or error.
    • Close client fds on epoll_ctl add failure; keep doEpollIndefinitely returning consistently.
    • Track/untrack fds and close safely; tolerate epoll_ctl DEL on closed/not-found fds.
  • Dependencies

    • Ship prebuilt xconnectorpatch JNI library (arm64-v8a, armeabi-v7a) and load via XConnectorEpollNative; Java now calls through this bridge.
    • Keep externalNativeBuild disabled; APK includes the prebuilt libs from app/src/main/jniLibs.

Written for commit 9627218. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added an optional native library integration and Java native bindings to enable native-backed socket handling.
  • Bug Fixes

    • Improved socket connection reliability with retry-on-interrupt behavior and better handling of transient/non-ready states.
    • Enhanced detection of socket read errors, connection shutdowns, and clearer failure logging.
    • Ensured more reliable resource tracking and orderly closure of file descriptors.
  • Chores

    • Prepared build configuration to support the new native component (not enabled by default).

@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 03de5473-0630-4cdc-9f9f-d5259aee31e9

📥 Commits

Reviewing files that changed from the base of the PR and between ea694c1 and 9627218.

⛔ Files ignored due to path filters (2)
  • app/src/main/jniLibs/arm64-v8a/libxconnectorpatch.so is excluded by !**/*.so
  • app/src/main/jniLibs/armeabi-v7a/libxconnectorpatch.so is excluded by !**/*.so
📒 Files selected for processing (1)
  • app/build.gradle.kts
✅ Files skipped from review due to trivial changes (1)
  • app/build.gradle.kts

📝 Walkthrough

Walkthrough

Adds a new native library xconnectorpatch with JNI bindings and fd-tracking C implementation, refactors Java to call package-native wrappers, and enhances native epoll/poll loops with retry-on-EINTR, stricter error handling, and more logging.

Changes

Cohort / File(s) Summary
Build config
app/build.gradle.kts
Uncommented/added (commented) CMake configuration referencing src/main/cpp/xconnectorpatch/CMakeLists.txt and CMake version 3.22.1.
New native target
app/src/main/cpp/xconnectorpatch/CMakeLists.txt
Adds CMake script declaring xconnectorpatch shared library target.
Native JNI implementation
app/src/main/cpp/xconnectorpatch/xconnectorpatch.c
New C implementation providing JNI bindings: fd tracking (FdTracker), AF_UNIX socket creation, epoll/eventfd creation, add/remove fd to epoll, blocking epoll/poll loops, retry-on-EINTR, logging, and JNI callbacks to Java.
Existing native improvements
app/src/main/cpp/winlator/xconnector_epoll.c
Introduces waitForEpollEvents/waitForPollEvents retry wrappers, uses them across epoll/poll flows, refines accept()/poll error handling (EAGAIN/EWOULDBLOCK, POLLERR/HUP/NVAL), logs epoll_ctl failures, and changed JNI closeFd signature to accept jclass.
Java JNI bridge
app/src/main/java/com/winlator/xconnector/XConnectorEpollNative.java
New package-private class declaring and loading native methods for the xconnectorpatch library (create sockets/epoll/eventfd, add/remove fd, doEpollIndefinitely, waitForSocketRead, closeFd).
Java refactor
app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java
Replaces previous native methods with Java wrappers delegating to XConnectorEpollNative, introduces closeTrackedFd and updates call sites to use the new native bridge; preserves external control flow but shifts native surface.

Sequence Diagram

sequenceDiagram
    participant Java as XConnectorEpoll (Java)
    participant JNI as XConnectorEpollNative (JNI)
    participant Native as xconnectorpatch.c (C)
    participant Sys as Kernel (epoll/poll/accept/close)

    Java->>JNI: createAFUnixSocket(path)
    JNI->>Native: Java_createAFUnixSocket
    Native->>Sys: socket()/bind()/listen()
    Sys-->>Native: serverFd
    Native-->>JNI: serverFd
    JNI-->>Java: serverFd

    Java->>JNI: createEpollFd()
    JNI->>Native: Java_createEpollFd
    Native->>Sys: epoll_create1()
    Sys-->>Native: epollFd
    Native-->>JNI: epollFd
    JNI-->>Java: epollFd

    Java->>JNI: doEpollIndefinitely(this, epollFd, serverFd, addClientToEpoll)
    JNI->>Native: Java_doEpollIndefinitely
    loop until shutdown
        Native->>Sys: waitForEpollEvents (epoll_wait with retry)
        Sys-->>Native: events
        alt new connection
            Native->>Sys: accept()
            Sys-->>Native: clientFd
            Native->>Native: FdTracker stores clientFd
            Native->>JNI: handleNewConnection callback
            JNI-->>Java: onNewConnection()
        else existing client readable
            Native->>Native: detect readable via poll/epoll
            Native->>JNI: handleExistingConnection callback
            JNI-->>Java: onExistingConnection()
        end
    end
    Native->>JNI: return status
    JNI-->>Java: doEpollIndefinitely returns

    Java->>JNI: closeFd(fd)
    JNI->>Native: Java_closeFd
    Native->>Sys: close(fd)
    Sys-->>Native: closed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Hopping through bytes in the native night,

FdTracker keeps sockets snug and tight.
Retries on interrupts, logs shining bright,
Java calls C, they dance till light.
A patch, a hop — everything's alright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: preserve xconnector waits across suspend interrupts' clearly and concisely describes the main change: making xconnector epoll/poll waits resilient to EINTR interrupts from device suspend, which is the central objective of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/cpp/xconnectorpatch/xconnectorpatch.c">

<violation number="1" location="app/src/main/cpp/xconnectorpatch/xconnectorpatch.c:115">
P2: Missing null/exception check after GetStringUTFChars can lead to a native crash if it returns NULL (e.g., OOM or null jstring).</violation>

<violation number="2" location="app/src/main/cpp/xconnectorpatch/xconnectorpatch.c:116">
P2: sockaddr_un length is computed from the full path string even though sun_path is truncated, and it omits the terminating NUL. For long paths, addrLength can exceed the actual buffer/sizeof(sockaddr_un), leading to bind() failures. Compute length from the truncated path and include the NUL byte.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread app/src/main/cpp/xconnectorpatch/xconnectorpatch.c
Comment thread app/src/main/cpp/xconnectorpatch/xconnectorpatch.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
app/src/main/cpp/xconnectorpatch/xconnectorpatch.c (2)

252-259: Performance: Repeated GetMethodID lookup on hot path.

GetObjectClass and GetMethodID are called on every poll wake. Since waitForSocketRead is invoked in a loop (per the Java code in handleNewConnection), this introduces unnecessary overhead.

Consider caching the jmethodID at the start of the method (or when the connector object is first seen):

⚡ Proposed optimization
 JNIEXPORT jboolean JNICALL
 Java_com_winlator_xconnector_XConnectorEpollNative_waitForSocketRead(JNIEnv *env, jclass clazz, jobject connector, jint clientFd, jint shutdownFd) {
+    // Cache method ID lookup outside hot path
+    static jmethodID handleExistingConnection = NULL;
+    if (handleExistingConnection == NULL) {
+        jclass connectorClass = (*env)->GetObjectClass(env, connector);
+        handleExistingConnection = (*env)->GetMethodID(env, connectorClass, "handleExistingConnection", "(I)V");
+        if (handleExistingConnection == NULL) {
+            LOGD("failed to resolve handleExistingConnection callback");
+            return JNI_FALSE;
+        }
+    }
+
     struct pollfd pfds[2];
     // ... poll logic ...

     if (pfds[0].revents & POLLIN) {
-        jclass connectorClass = (*env)->GetObjectClass(env, connector);
-        jmethodID handleExistingConnection = (*env)->GetMethodID(env, connectorClass, "handleExistingConnection", "(I)V");
-        if (handleExistingConnection == NULL) {
-            LOGD("failed to resolve handleExistingConnection callback");
-            return JNI_FALSE;
-        }
         (*env)->CallVoidMethod(env, connector, handleExistingConnection, clientFd);
     }

Note: This assumes the same class is always passed. If different subclasses could be passed, you'd need per-class caching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/cpp/xconnectorpatch/xconnectorpatch.c` around lines 252 - 259,
GetObjectClass and GetMethodID are being called inside the hot loop of
waitForSocketRead for every poll wake (resolving handleExistingConnection each
time); cache the jmethodID (and optionally the jclass) when the connector object
is first seen (or at start of waitForSocketRead) and reuse that cached method ID
for subsequent CallVoidMethod invocations to avoid repeated lookups; adjust
logic around connector/handleExistingConnection resolution so you only call
GetMethodID once and fall back to resolving again if the cached class/method
become invalid.

72-100: Code duplication with xconnector_epoll.c.

The waitForEpollEvents and waitForPollEvents functions are nearly identical to those in app/src/main/cpp/winlator/xconnector_epoll.c. While being static avoids linker issues, this creates maintenance burden - any bug fixes must be applied to both files.

Consider extracting these into a shared header or source file that both can include.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/cpp/xconnectorpatch/xconnectorpatch.c` around lines 72 - 100,
Extract the duplicated blocking-retry logic into a single shared implementation:
create a new source (e.g., xconnector_wait.c) containing non-static
implementations of waitForEpollEvents and waitForPollEvents and a header (e.g.,
xconnector_wait.h) that declares those functions; remove the duplicate static
definitions from xconnectorpatch.c and xconnector_epoll.c, include
xconnector_wait.h in both files, and update the build files so xconnector_wait.c
is compiled/linked into both targets. Ensure function signatures remain
identical, preserve logging behavior (LOGD and errno handling), and remove the
static keyword so there is only one canonical definition.
app/src/main/cpp/xconnectorpatch/CMakeLists.txt (1)

1-7: LGTM - Minimal CMake configuration.

The CMake configuration is functional. For improved code quality, consider adding compiler warning flags.

💡 Optional: Add compiler warnings
 cmake_minimum_required(VERSION 3.22.1)

 project(XConnectorPatch C)

+# Enable warnings for better code quality
+target_compile_options(xconnectorpatch PRIVATE -Wall -Wextra)
+
 add_library(xconnectorpatch SHARED xconnectorpatch.c)

 target_link_libraries(xconnectorpatch log)

Note: The target_compile_options must come after add_library.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/cpp/xconnectorpatch/CMakeLists.txt` around lines 1 - 7, Add
compiler warning flags to the C target after the library is created: update the
CMakeLists (after add_library(xconnectorpatch ...)) to call
target_compile_options(xconnectorpatch ...) for the C language and include
sensible flags (e.g., -Wall -Wextra -Wpedantic and optionally -Werror) so the
xconnectorpatch target is built with warnings enabled; keep the existing
target_link_libraries(xconnectorpatch log) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/cpp/xconnectorpatch/xconnectorpatch.c`:
- Around line 19-70: The static fdTracking array is accessed concurrently by
trackFd, untrackFd, and closeTrackedFd and needs synchronization; add a
pthread_mutex_t (or std::mutex if C++) as a static/global (e.g.,
fdTrackingMutex), initialize it appropriately, then lock the mutex at the start
of trackFd and untrackFd and around the untrack-check in closeTrackedFd (unlock
before calling close() if you want to avoid holding the lock during the syscall,
or keep it locked if necessary), and ensure to unlock on all return paths so all
accesses to fdTracking and its fields are protected and race-free.

---

Nitpick comments:
In `@app/src/main/cpp/xconnectorpatch/CMakeLists.txt`:
- Around line 1-7: Add compiler warning flags to the C target after the library
is created: update the CMakeLists (after add_library(xconnectorpatch ...)) to
call target_compile_options(xconnectorpatch ...) for the C language and include
sensible flags (e.g., -Wall -Wextra -Wpedantic and optionally -Werror) so the
xconnectorpatch target is built with warnings enabled; keep the existing
target_link_libraries(xconnectorpatch log) intact.

In `@app/src/main/cpp/xconnectorpatch/xconnectorpatch.c`:
- Around line 252-259: GetObjectClass and GetMethodID are being called inside
the hot loop of waitForSocketRead for every poll wake (resolving
handleExistingConnection each time); cache the jmethodID (and optionally the
jclass) when the connector object is first seen (or at start of
waitForSocketRead) and reuse that cached method ID for subsequent CallVoidMethod
invocations to avoid repeated lookups; adjust logic around
connector/handleExistingConnection resolution so you only call GetMethodID once
and fall back to resolving again if the cached class/method become invalid.
- Around line 72-100: Extract the duplicated blocking-retry logic into a single
shared implementation: create a new source (e.g., xconnector_wait.c) containing
non-static implementations of waitForEpollEvents and waitForPollEvents and a
header (e.g., xconnector_wait.h) that declares those functions; remove the
duplicate static definitions from xconnectorpatch.c and xconnector_epoll.c,
include xconnector_wait.h in both files, and update the build files so
xconnector_wait.c is compiled/linked into both targets. Ensure function
signatures remain identical, preserve logging behavior (LOGD and errno
handling), and remove the static keyword so there is only one canonical
definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db1f28f6-6cb0-44dc-b1cb-d85754416a7e

📥 Commits

Reviewing files that changed from the base of the PR and between 460dadc and ea694c1.

📒 Files selected for processing (6)
  • app/build.gradle.kts
  • app/src/main/cpp/winlator/xconnector_epoll.c
  • app/src/main/cpp/xconnectorpatch/CMakeLists.txt
  • app/src/main/cpp/xconnectorpatch/xconnectorpatch.c
  • app/src/main/java/com/winlator/xconnector/XConnectorEpoll.java
  • app/src/main/java/com/winlator/xconnector/XConnectorEpollNative.java

Comment thread app/src/main/cpp/xconnectorpatch/xconnectorpatch.c
@utkarshdalal

Copy link
Copy Markdown
Owner

would be good to get this in, can we confirm that it fixes the issue before i merge?

@madskoelbaek

Copy link
Copy Markdown

If you can get me an apk I will gladly test it out :)

@madskoelbaek

Copy link
Copy Markdown

I bult this branch myself and can confirm it works on Odin 3 😍

@bendavid1

Copy link
Copy Markdown

confirmed working on galaxy s25 ultra

@utkarshdalal

Copy link
Copy Markdown
Owner

@xXJSONDeruloXx I think we'll need to include the built .so in here, we don't rebuild when making the APK

@xXJSONDeruloXx

xXJSONDeruloXx commented Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

We should build from source wherever possible instead of bundling bins imo. @utkarshdalal

@utkarshdalal

utkarshdalal commented Mar 27, 2026

Copy link
Copy Markdown
Owner

yes but we don't have the source for winlator11.so

Please do include the built file in the PR and I can merge

@xXJSONDeruloXx

Copy link
Copy Markdown
Contributor Author

@utkarshdalal done, but aside from winlator11.so I want to revisit building from source as standard, the less opaque the codebase the better

@utkarshdalal

Copy link
Copy Markdown
Owner

Sure, I agree it's a better practice. The source for most of the .sos is what is in the winlator bionic repo

@xXJSONDeruloXx

Copy link
Copy Markdown
Contributor Author

Yet if we make changes over time but then bundle the bins even if source is in the codebase it's unclear where a bin blobs diff is from unless you trudge through merges. Especially when pr gets squashed

@utkarshdalal

Copy link
Copy Markdown
Owner

Yup, I'm in agreement with you. Was just saying where we need to pull in the native code from :D

@xXJSONDeruloXx

Copy link
Copy Markdown
Contributor Author

Other than that, .so's added so lmk if lgtm!

@utkarshdalal
utkarshdalal merged commit 3ccfb82 into utkarshdalal:master Mar 28, 2026
3 checks passed
@xXJSONDeruloXx
xXJSONDeruloXx deleted the fix/suspend-epoll-eintr branch March 28, 2026 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants