Skip to content

Comments

fix: 🐛 #188 (android): Add null checks to prevent NullPointerException…#227

Merged
Rapsssito merged 1 commit intoRapsssito:masterfrom
louiscavalcante:master
Jan 1, 2026
Merged

fix: 🐛 #188 (android): Add null checks to prevent NullPointerException…#227
Rapsssito merged 1 commit intoRapsssito:masterfrom
louiscavalcante:master

Conversation

@louiscavalcante
Copy link
Contributor

This PR closes Issue #188

Problem

The app crashes with a NullPointerException when the socket becomes null during receiver task operations:

java.lang.NullPointerException: Attempt to invoke virtual method
'java.io.InputStream java.net.Socket.getInputStream()' on a null object reference
    at com.asterinet.react.tcpsocket.TcpSocketClient$TcpReceiverTask.run(TcpSocketClient.java:251)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1154)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:652)
    at java.lang.Thread.run(Thread.java:1563)

Root Cause Analysis

This is a race condition in TcpReceiverTask.run(). The socket can become null between retrieval and usage due to:

  1. destroy() called concurrently: If destroy() is called while the receiver task is starting or running, it sets socket = null (line 190) while the receiver thread may be accessing it.

  2. Timing window: The receiver task runs in a separate thread (listenExecutor). There's a window between when getSocket() returns and when socket.getInputStream() is called where another thread can call destroy().

  3. No synchronization: The socket field is accessed from multiple threads without synchronization:

    • Main thread: connect(), destroy(), startTLS()
    • Receiver thread: TcpReceiverTask.run()
    • Write thread: write()

Triggering Scenario

In my case, this occurred when using react-native-zeroconf for mDNS service discovery followed by immediate TCP connection:

  1. mDNS scan discovers a network service
  2. User selects the service → scan stops
  3. App immediately creates TCP socket to connect to the discovered service
  4. Race condition: network stack instability from scan cleanup causes socket creation issues
  5. Receiver task starts but socket is null → NPE

Changes

1. Add null guard at start of TcpReceiverTask.run()

@Override
public void run() {
    int socketId = clientSocket.getId();
    Socket socket = clientSocket.getSocket();

    // Guard against null socket - can happen if destroy() is called
    // before the receiver task starts, or if socket creation failed
    if (socket == null) {
        return;
    }
    // ... rest of method
}

2. Add null check in catch block

The catch block also accessed socket.isClosed() without null checking:

} catch (IOException | InterruptedException ioe) {
    if (receiverListener != null && socket != null && !socket.isClosed() && !clientSocket.closed) {
        receiverListener.onError(socketId, ioe);
    }
}

3. Add null checks in pause() and resume() methods

These methods access receiverTask without null checking. If called before startListening() completes, they would throw NPE:

public void pause() {
    if (receiverTask != null) {
        receiverTask.pause();
    }
}

public void resume() {
    if (receiverTask != null) {
        receiverTask.resume();
    }
}

Why This Fix

This is a defensive fix that prevents the crash without changing the library's behavior. The alternative would be to add proper thread synchronization (synchronized blocks or locks), but that would be a more invasive change with potential performance implications.

The write() method already has this pattern (lines 157-159):

if (socket == null) {
    receiverListener.onError(getId(), new IOException("Attempted to write to closed socket"));
    return;
}

This PR applies the same defensive pattern to the receiver task.

Testing

Tested by:

  1. Starting mDNS discovery for network services
  2. Selecting a discovered service (stops discovery, immediately creates TCP connection)
  3. Verifying no NullPointerException occurs
  4. Confirming normal socket operations work correctly

Impact

  • Minimal: Only adds null checks, no behavioral changes
  • No breaking changes: Existing code continues to work identically
  • No performance impact: Single null check is negligible

@louiscavalcante
Copy link
Contributor Author

louiscavalcante commented Dec 30, 2025

Currently testing these changes.

@louiscavalcante louiscavalcante changed the title fix: 🐛 (android): Add null checks to prevent NullPointerException… fix: 🐛 #188 (android): Add null checks to prevent NullPointerException… Dec 30, 2025
@louiscavalcante louiscavalcante marked this pull request as ready for review December 31, 2025 21:42
@louiscavalcante
Copy link
Contributor Author

Tested and ready for review. Happy new year! @Rapsssito

@Rapsssito Rapsssito merged commit 27203eb into Rapsssito:master Jan 1, 2026
1 check passed
@louiscavalcante
Copy link
Contributor Author

@Rapsssito the semantic release of this PR failed. Something about your NPM credentials.

@Rapsssito
Copy link
Owner

@louiscavalcante, Wops! My bad. NPM just removed classic tokens. I will create a new one ASAP.

github-actions bot pushed a commit that referenced this pull request Jan 3, 2026
## [6.3.1](v6.3.0...v6.3.1) (2026-01-03)

### Bug Fixes

* **Android:** prevent NullPointerException in TcpReceiverTask ([#227](#227)) ([27203eb](27203eb))
@github-actions
Copy link

github-actions bot commented Jan 3, 2026

🎉 This PR is included in version 6.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants