Search code examples
javaandroidretrofit2rx-java2

java.net.SocketException: Socket closed not caught by RxJava onError (Single)


I am doing something like this: in my Fragment, I am observing a LiveData exposed by a ViewModel that encapsulates different UI states like error, loading, success state, etc. Within the said ViewModel, I start an RxJava2 stream that kicks off a chain of REST API requests via Retrofit:

    public LiveData<UIState> doSomething() {
        compositeDisposable.add(somethingRepo.doA()
            .andThen(somethingRepo.doB())
            .andThen(somethingRepo.doC())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .doOnSubscribe(disposable -> status.setValue(UIState.LOADING()))
            .subscribe(() -> status.setValue(UIState.SUCCESS()), error -> {
                Log.d("SettingsVM", "error: " + error.getClass().getSimpleName());

                if(error instanceof VersionAlreadyExistsException) {
                    status.setValue(UIState.VERSION_EXISTS());
                } else {
                    status.setValue(UIState.ERROR(error.getMessage()));
                }
            })
        );
        return status;
    }

However, according to my custom logger that implements HttpLoggingInterceptor.Logger, I am getting a java.net.SocketException: Socket closed during somethingRepo.doA(), but it seems that I am not getting the Exception in doSomething()'s onError. Here is the full somethingRepo.doA():

public Completable doA() {
    return api.doRequest(paramRepository.getUrl(false),
        paramRepository.getAuthorization(),
        paramRepository.getName())
        .flatMapCompletable(requestResponse-> {
            if(paramRepository.getVersion().contentEquals(requestResponse.getVersion())) {
                return Completable.error(new VersionAlreadyExistsException());
            } else {
                return Completable.complete();
            }
        });
}

api.doRequest is a GET request that looks like this:

@GET
Single<RequestResponse> doRequest (
        @Url String url,
        @Header("authorization") String authorization,
        @Query("merch") String name);

Interestingly, on another Android app, I sent a POST to the same URL I am using in the previous example and what I got was HTTP FAILED: java.net.ConnectException: Failed to connect to /180.232.98.122:7443.

I am aware of a similar issue being opened in the official Retrofit repo and a similar SO question here, but those two are unresolved, thus I would like to hear if anyone here has encountered the same behavior.


Solution

  • I figured out what the problem was. Firstly, I implemented an "inactivity timer" that returns all screens (I use Fragments for screens) to the first/home screen in my app. So let's say a value of 30 seconds was set to the timer, if no interaction with the UI happens and the timer reaches 30 secs then my app goes back to the home screen.

    Secondly, the ViewModel where I kick off the requests is tied to a Fragment since it implements DefaultLifecycleObserver, which means that it will receive LifecycleOwner changes/callbacks, provided I call this: getViewLifecycleOwner().getLifecycle().addObserver(viewModel) in my Fragment#onViewCreated. I then override onStop(@NonNull LifecycleOwner owner) in the ViewModel and call compositeDisposable.clear(), which means I want to sort of cancel all pending API request I made using RxJava2 once the Fragment my ViewModel is tied to receives onStop.

    The final piece of info is that the timeout value for my OkHttp client is greater than the inactivity timer value, so the inactivity timer abruptly causes my request API to be cancelled even before it finishes.

    To summarize, here is what happens: when the inactivity timer is up, the request API Fragment's onStop is called as the screen changes back to the home screen, and then the ViewModel calls compositeDisposable.clear() which disposes the current RxJava disposable, thus I am unable to receive anything on onError.