termux/termux-app · error · IOException

[dynamic] JniResult.getErrorString() — composed at runtime:

Error message

[dynamic] JniResult.getErrorString() — composed at runtime: "Retval: {retval}\nErrno: {errno}\nErrmsg: {errmsg}"

What it means

Thrown by LocalServerSocket.close() when the JNI closeSocket call returns null or a JniResult with retval != 0. The message is composed at runtime by JniResult.getErrorString() as 'Retval: {retval}\nErrno: {errno}\nErrmsg: {errmsg}'. This signals the native close of the server socket fd failed (e.g. EBADF because the fd was already closed, or a JNI-level fault).

Source

Thrown at termux-shared/src/main/java/com/termux/shared/net/socket/local/LocalServerSocket.java:168

            if (logErrorMessage)
                Logger.logErrorExtended(LOG_TAG, error.getErrorLogString());
            return error;
        }

        return null;
    }

    /** Implementation for {@link Closeable#close()} to close server socket. */
    @Override
    public synchronized void close() throws IOException {
        Logger.logDebug(LOG_TAG, "close");

        int fd = mLocalSocketRunConfig.getFD();

        if (fd >= 0) {
            JniResult result = LocalSocketManager.closeSocket(mLocalSocketRunConfig.getLogTitle() + " (server)", fd);
            if (result == null || result.retval != 0) {
                throw new IOException(JniResult.getErrorString(result));
            }
            // Update fd to signify that server socket has been closed
            mLocalSocketRunConfig.setFD(-1);
        }
    }

    /**
     * Delete server socket file if not an abstract namespace socket. This will cause any existing
     * running server to stop.
     */
    private Error deleteServerSocketFile() {
        if (!mLocalSocketRunConfig.isAbstractNamespaceSocket())
            return FileUtils.deleteSocketFile(mLocalSocketRunConfig.getTitle() + " server socket file", mLocalSocketRunConfig.getPath(), true);
        else
            return null;
    }

    /** Listen and accept new {@link LocalClientSocket}. */

View on GitHub (pinned to 3df69d1da1)

Solutions

  1. Make close() idempotent: skip the JNI call when fd < 0 (it is already set to -1 after a successful close).
  2. Ensure close() is called exactly once (use try-with-resources on the LocalServerSocket).
  3. Inspect the JniResult errno/errmsg in the IOException message to identify EBADF vs. native fault.
  4. If the native library failed to load, load it before any socket operation.

Example fix

// before
if (fd >= 0) {
    JniResult result = LocalSocketManager.closeSocket(mLocalSocketRunConfig.getLogTitle() + " (server)", fd);
    if (result == null || result.retval != 0) {
        throw new IOException(JniResult.getErrorString(result));
    }
    mLocalSocketRunConfig.setFD(-1);
}

// after (idempotent + log instead of throwing on already-closed fd)
if (fd >= 0) {
    JniResult result = LocalSocketManager.closeSocket(mLocalSocketRunConfig.getLogTitle() + " (server)", fd);
    mLocalSocketRunConfig.setFD(-1);
    if (result == null || result.retval != 0) {
        Logger.logWarn(LOG_TAG, "closeSocket returned error on fd " + fd + ": " + JniResult.getErrorString(result));
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (mLocalSocketRunConfig.getFD() < 0) {
    // already closed; nothing to do
    return;
}

Try / catch

try {
    serverSocket.close();
} catch (IOException e) {
    // log only; close failures on an already-tearing-down socket are usually benign
    Logger.logWarn(LOG_TAG, "close() failed: " + e.getMessage());
}

Prevention

When it happens

Trigger: close() called twice (fd already closed, second close yields EBADF); the fd was invalidated elsewhere; JNI closeSocket raised an exception/returned a non-zero retval; the fd value is stale after the server was stopped.

Common situations: Double-close in a finally block; server socket already torn down by the manager; fd overwritten/corrupted; native lib not loaded so JNI returns an error result.

Related errors


AI-assisted analysis of termux/termux-app@3df69d1da1 (2026-08-13). Data as JSON: /api/errors/576e653a15cd9258. Report an issue: GitHub.