AutonomousApps
AutonomousApps

Reputation: 4389

"Impossible" NPE in a multi-threaded situation

I have some inherently asynchronous code in an Android app. I'm using RxJava2. The class Thing is not under my control (but ThingFactory is). In one method (createThing()), a new Thing is instantiated. Thing's constructor does some work and, when it is complete, notifies us via a callback onThingInitialized(). At the point that callback is called, we should be guaranteed that thing exists. In the callback, I schedule work to happen on a separate thread (in this case, using RxJava2, but I don't think it should matter). There is nowhere in this code that I call anything like thing = null. So, once it's set, it's set forever.

I threw a volatile onto it because the instance does get updated, but never nulled. If I'm mis-using it, please feel free to berate me.

public class UsesAThing implements ThingCallbacks {

    private volatile Thing thing; // I feel like I don't understand 'volatile'

    // I call this method
    public void createThing() {
        thing = thingFactory.newThing(param1, param2);
    }

    // Thing's constructor does some work and notifies us when it's done
    @Override
    public void onThingInitialized() {
        // Called on main thread, but I want to do some IO work, so:
        Schedulers.io().scheduleDirect(() -> {
            thing.doStuff(); // NPE!
        });
    }
}

How is an NPE possible there?

EDIT:

Thing's constructor does its work asynchronously. As I said, this is in an Android environment, so the work it's actually doing is binding to a Service. When the Service is bound, its ServiceConnection::onServiceConnected() callback is hit, which itself actually fires up an AsyncTask which, in its onPostExecute() callback, calls the onThingInitialized() callback.

EDIT 2:

I should also note that this NPE doesn't happen all the time. I've run through this code hundreds of time, and I've only seen it occur once.

EDIT 3: Sample calling code

I didn't provide sample code because it's about as simple as one might imagine, but here's what it looks like:

Flowable.just(1)
    .subscribeOn(Schedulers.io())
    .subscribe(i -> createThing());

Upvotes: 0

Views: 130

Answers (1)

Kevin Krumwiede
Kevin Krumwiede

Reputation: 10308

If I understand your comment, createThing() is called in a worker thread. It's unlikely but possible that the thread scheduler will halt this worker thread after the Thing constructor initiates the sequence of events that leads to the callback, but before newThing() returns and thing is assigned. If the whole callback sequence runs before the thread calling createThing() runs again, you will see this NPE.

To test this theory, first create a test that runs repeatedly to reproduce the issue. Then change it so createThing() is called in the main thread and see if the problem goes away. That would be a workaround, not a fix. But a real fix would involve not doing work in Thing's constructor, which you stated is out of your control.

Upvotes: 4

Related Questions