Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DefaultHttpDataSource: A connection to XXX was leaked. Did you forget to close a response body? #3726

Closed
chariotsystems opened this issue Jan 18, 2018 · 16 comments
Assignees

Comments

@chariotsystems
Copy link

chariotsystems commented Jan 18, 2018

com.google.android.exoplayer2.upstream.DefaultHttpDataSource

will leak connections for a 404 response from the server

W/OkHttpClient: A connection to http://203.36.100.86/ was leaked. Did you forget to close a response body?
W/OkHttpClient: A connection to http://203.36.100.37/ was leaked. Did you forget to close a response body?
W/OkHttpClient: A connection to http://127.0.0.1:8080/ was leaked. Did you forget to close a response body?

The reason is that the okhttp3.internal.connection.RealConnection keeps track of which of the three streams (input, output and error) it believes its client has opened. If they are not closed before disconnect then it sees this as an error.

The fix to DefaultDataSource is as follows change close to being as shown below:
Also instead of calling connection.disconnect(); or closeConnectionQuietly() directly - just call close() instead.

  @Override
    public void close() throws HttpDataSourceException {
        try {
            if (connection != null) {
                if (inputStream == null) {
                    try {
                        inputStream = connection.getInputStream();
                    } catch (IOException e) {
                    }
                }
                if (inputStream != null) {
                    maybeTerminateInputStream(connection, bytesRemaining());
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_CLOSE);
                    }
                }
                try {
                    OutputStream outputStream = connection.getOutputStream();
                    if (outputStream != null) {
                        outputStream.close();
                    }
                } catch (Exception e) {
                }
                try {
                    InputStream errorStream = connection.getErrorStream();
                    if (errorStream != null) {
                        errorStream.close();
                    }
                } catch (Exception e) {
                }
            }
        } finally {
            inputStream = null;
            closeConnectionQuietly();
            if (opened) {
                opened = false;
                if (listener != null) {
                    listener.onTransferEnd(this);
                }
            }
        }
    }
@chariotsystems
Copy link
Author

I am using my own modified version of the DefaultHttpDataSource as I need to support unicast and multicast streams and cross over - so there is no urgency for me that this be fixed - but this fix will help others :)

@ojw28 ojw28 self-assigned this Jan 19, 2018
@ojw28
Copy link
Contributor

ojw28 commented Jan 19, 2018

DefaultHttpDataSource relies on the underlying platform network stack, via HttpURLConnection. So I think the logging you're referring to must be coming from the underlying platform. I'm unable to reproduce the issue locally, so I suspect it may only occur on certain devices and/or versions of Android, and that it's a platform bug specific to those.

The reason is that the okhttp3.internal.connection.RealConnection keeps track of which of the three streams (input, output and error) it believes its client has opened. If they are not closed before disconnect then it sees this as an error.

Android has (by default) never used okhttp3 as the underlying platform network stack. So unless the manufacturer of the device you're using has replaced the network stack, I think it's more likely this is coming from a version of okhttp 2.x.x, which has been used (version 2.7.5 includes the same logging).

For us to further investigate, please could you provide a bug report captured using adb bugreport shortly after reproducing the issue, details of the device and Android version, and concrete reproduction steps? Thanks.

@chariotsystems
Copy link
Author

chariotsystems commented Jan 22, 2018 via email

@chariotsystems
Copy link
Author

chariotsystems commented Jan 22, 2018

removed wrong file

@chariotsystems
Copy link
Author

The solution provided in LeakyHttpDataSource doesn't comprehensively call close for all instances where connection.disconnect() is called - so ensure that this is done in your final solution.

@ojw28
Copy link
Contributor

ojw28 commented Jan 22, 2018

I still don't understand this:

  • There is nothing in the okhttp GitHub issue you reference that saying that you need to get and close all of the streams explicitly, as far as I can see. That issue relates to okhttp3 and only says that you need to close the response body. This is not relevant to the case of dealing with okhttp2 via HttpURLConnection; I think that calling connection.disconnect() should be enough. If you know of a comment relating to what you're doing here, please could you point out exactly where it is.
  • I don't see a willLeak boolean anywhere in the attached file.
  • I don't think the S8 Oreo release has rolled out yet. Is this a beta version, or a non-official build provided by someone other than Samsung? I suspect this is an issue specific to the version of Android that you're running on the device, since I don't think the ExoPlayer code is doing anything wrong. If this is a beta version or non-official build, that becomes even more likely.

Please clarify.

@ojw28
Copy link
Contributor

ojw28 commented Jan 22, 2018

Please also attach a bugreport attached with adb bugreport, as requested in the original issue template and above.

@chariotsystems
Copy link
Author

@chariotsystems
Copy link
Author

chariotsystems commented Jan 22, 2018

A big apology here. I seem to have attached an earlier version of the file without the fix.
This is what I have modified as well as the implementation of the close() method details as given above. Note that the url is deliberately mangled to force the makeConnection's bowels to cough up an error and hence implicitly open the error stream.

 private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException {
        URL url = new URL(dataSpec.uri.toString());
        byte[] postBody = dataSpec.postBody;
        long position = dataSpec.position;
        long length = dataSpec.length;
        boolean allowGzip = dataSpec.isFlagSet(DataSpec.FLAG_ALLOW_GZIP);

        for (int i = 0; i < 20; i++) {
            HttpURLConnection connection = makeConnection(
                    new URL(dataSpec.uri.toString() + "bad"), postBody, position, length, allowGzip, false /* followRedirects */);
            if (willLeak) {
                connection.disconnect();
            } else {
                close(connection);
            }
        }

        if (!allowCrossProtocolRedirects) {
            // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection

@chariotsystems
Copy link
Author

chariotsystems commented Jan 22, 2018

Yes I am using an advanced Engineering Build of S8 from Samsung. You might not have access to such a device and build. So I will reproduce on an S7 for you later and attach the adb bugreport as requested.

I presume you know how to run this LeakyHttpDataSource with a multi segment dash manifest.
That you know to modify the HttpDataSourceFactory to return this rather than the DefaultHttpDataSource.

 @Override
    public LeakyHttpDataSource createDataSource() {
        return new LeakyHttpDataSource(userAgent, null, listener, connectTimeoutMillis,
                readTimeoutMillis);
      
    }

@ojw28
Copy link
Contributor

ojw28 commented Jan 22, 2018

Thanks. I can reproduce this with the new makeConnection code snippet. It's not specific to the S8 build. Marking as a bug.

@ojw28 ojw28 added bug and removed need more info labels Jan 22, 2018
@chariotsystems
Copy link
Author

That's great :)

@ojw28 ojw28 changed the title DefaultHttpDataSource DefaultHttpDataSource: A connection to XXX was leaked. Did you forget to close a response body? Jan 24, 2018
@ojw28
Copy link
Contributor

ojw28 commented Jan 24, 2018

I dug into this a bit more. It looks like it was decided this was a platform issue, rather than anything at the app level. As a result, the logged warning was removed from the platform in https://android-review.googlesource.com/c/platform/external/okhttp/+/460418. I think this will probably land in Android P.

I have no objection to explicitly getting and closing the streams to avoid the error, but only if this occurs no overhead. It seems possible that calling getXStream might actually cause the stream to be allocated or something, just so we can immediately release it. We need to double check that this definitely wont happen before making the change, since if it does the harmless log message is preferable.

@chariotsystems
Copy link
Author

chariotsystems commented Jan 25, 2018 via email

@ojw28
Copy link
Contributor

ojw28 commented May 9, 2018

I don't think we're going to spend any time fixing this. The logged warning was removed from the platform in newer releases, and in older versions it seems it's considered harmless.

You can also avoid the issue by using one of our two network stack extensions (the OkHttp extension and the Cronet extension). It's probably a good idea to use one of those regardless, so you're using the same network stack across all versions of Android on which your app runs.

@ojw28 ojw28 closed this as completed May 9, 2018
@chariotsystems
Copy link
Author

chariotsystems commented May 10, 2018 via email

@google google locked and limited conversation to collaborators Sep 11, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants