mirror of
https://github.com/zhigang1992/AndroidVideoCache.git
synced 2026-03-06 22:32:57 +08:00
🐛 don't close source after processing partial request without cache #43
prevent invalid calling source.close() in different threads to avoid crashes on Lollipop (#37, #29, #63, #66)
This commit is contained in:
BIN
files/space.jpg
Normal file
BIN
files/space.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -83,18 +83,18 @@ class HttpProxyCache extends ProxyCache {
|
||||
}
|
||||
|
||||
private void responseWithoutCache(OutputStream out, long offset) throws ProxyCacheException, IOException {
|
||||
HttpUrlSource newSourceNoCache = new HttpUrlSource(this.source);
|
||||
try {
|
||||
HttpUrlSource source = new HttpUrlSource(this.source);
|
||||
source.open((int) offset);
|
||||
newSourceNoCache.open((int) offset);
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
int readBytes;
|
||||
while ((readBytes = source.read(buffer)) != -1) {
|
||||
while ((readBytes = newSourceNoCache.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, readBytes);
|
||||
offset += readBytes;
|
||||
}
|
||||
out.flush();
|
||||
} finally {
|
||||
source.close();
|
||||
newSourceNoCache.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,11 @@ public class HttpUrlSource implements Source {
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.disconnect();
|
||||
} catch (NullPointerException e) {
|
||||
// https://github.com/danikula/AndroidVideoCache/issues/32
|
||||
// https://github.com/danikula/AndroidVideoCache/issues/29
|
||||
throw new ProxyCacheException("Error disconnecting HttpUrlConnection", e);
|
||||
} catch (NullPointerException | IllegalArgumentException e) {
|
||||
String message = "Wait... but why? WTF!? " +
|
||||
"Really shouldn't happen any more after fixing https://github.com/danikula/AndroidVideoCache/issues/43. " +
|
||||
"If you read it on your device log, please, notify me danikula@gmail.com or create issue here https://github.com/danikula/AndroidVideoCache/issues.";
|
||||
throw new RuntimeException(message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
test/src/main/assets/space.jpg
Normal file
BIN
test/src/main/assets/space.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -13,9 +13,20 @@ import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.ASSETS_DATA_BIG_NAME;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_BIG_URL;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_SIZE;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.HTTP_DATA_URL;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadAssetFile;
|
||||
import static com.danikula.videocache.support.ProxyCacheTestUtils.loadTestData;
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
@@ -37,37 +48,22 @@ public class HttpProxyCacheTest {
|
||||
|
||||
@Test
|
||||
public void testProcessRequestNoCache() throws Exception {
|
||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL);
|
||||
FileCache cache = new FileCache(ProxyCacheTestUtils.newCacheFile());
|
||||
HttpProxyCache proxyCache = new HttpProxyCache(source, cache);
|
||||
GetRequest request = new GetRequest("GET /" + HTTP_DATA_URL + " HTTP/1.1");
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Socket socket = mock(Socket.class);
|
||||
when(socket.getOutputStream()).thenReturn(out);
|
||||
|
||||
proxyCache.processRequest(request, socket);
|
||||
Response response = new Response(out.toByteArray());
|
||||
Response response = processRequest(HTTP_DATA_URL, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
|
||||
|
||||
assertThat(response.data).isEqualTo(loadTestData());
|
||||
assertThat(response.code).isEqualTo(200);
|
||||
assertThat(response.contentLength).isEqualTo(ProxyCacheTestUtils.HTTP_DATA_SIZE);
|
||||
assertThat(response.contentLength).isEqualTo(HTTP_DATA_SIZE);
|
||||
assertThat(response.contentType).isEqualTo("image/jpeg");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessPartialRequestWithoutCache() throws Exception {
|
||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_URL);
|
||||
FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile());
|
||||
FileCache spyFileCache = Mockito.spy(fileCache);
|
||||
doThrow(new RuntimeException()).when(spyFileCache).read(any(byte[].class), anyLong(), anyInt());
|
||||
HttpProxyCache proxyCache = new HttpProxyCache(source, spyFileCache);
|
||||
GetRequest request = new GetRequest("GET /" + HTTP_DATA_URL + " HTTP/1.1\nRange: bytes=2000-");
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Socket socket = mock(Socket.class);
|
||||
when(socket.getOutputStream()).thenReturn(out);
|
||||
|
||||
proxyCache.processRequest(request, socket);
|
||||
Response response = new Response(out.toByteArray());
|
||||
String httpRequest = "GET /" + HTTP_DATA_URL + " HTTP/1.1\nRange: bytes=2000-";
|
||||
Response response = processRequest(HTTP_DATA_URL, httpRequest, spyFileCache);
|
||||
|
||||
byte[] fullData = loadTestData();
|
||||
byte[] partialData = new byte[fullData.length - 2000];
|
||||
@@ -76,6 +72,73 @@ public class HttpProxyCacheTest {
|
||||
assertThat(response.code).isEqualTo(206);
|
||||
}
|
||||
|
||||
@Test // https://github.com/danikula/AndroidVideoCache/issues/43
|
||||
public void testPreventClosingOriginalSourceForNewPartialRequestWithoutCache() throws Exception {
|
||||
HttpUrlSource source = new HttpUrlSource(HTTP_DATA_BIG_URL);
|
||||
FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile());
|
||||
HttpProxyCache proxyCache = new HttpProxyCache(source, fileCache);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(5);
|
||||
Future<Response> firstRequestFeature = processAsync(executor, proxyCache, "GET /" + HTTP_DATA_URL + " HTTP/1.1");
|
||||
Thread.sleep(100); // wait for first request started to process
|
||||
|
||||
int offset = 30000;
|
||||
String partialRequest = "GET /" + HTTP_DATA_URL + " HTTP/1.1\nRange: bytes=" + offset + "-";
|
||||
Future<Response> secondRequestFeature = processAsync(executor, proxyCache, partialRequest);
|
||||
|
||||
Response secondResponse = secondRequestFeature.get();
|
||||
Response firstResponse = firstRequestFeature.get();
|
||||
|
||||
byte[] responseData = loadAssetFile(ASSETS_DATA_BIG_NAME);
|
||||
assertThat(firstResponse.data).isEqualTo(responseData);
|
||||
|
||||
byte[] partialData = new byte[responseData.length - offset];
|
||||
System.arraycopy(responseData, offset, partialData, 0, partialData.length);
|
||||
assertThat(secondResponse.data).isEqualTo(partialData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessManyThreads() throws Exception {
|
||||
final String url = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/space.jpg";
|
||||
HttpUrlSource source = new HttpUrlSource(url);
|
||||
FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile());
|
||||
final HttpProxyCache proxyCache = new HttpProxyCache(source, fileCache);
|
||||
final byte[] loadedData = loadAssetFile("space.jpg");
|
||||
final Random random = new Random(System.currentTimeMillis());
|
||||
int concurrentRequests = 10;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(concurrentRequests);
|
||||
Future[] results = new Future[concurrentRequests];
|
||||
int[] offsets = new int[concurrentRequests];
|
||||
final CountDownLatch finishLatch = new CountDownLatch(concurrentRequests);
|
||||
final CountDownLatch startLatch = new CountDownLatch(1);
|
||||
for (int i = 0; i < concurrentRequests; i++) {
|
||||
final int offset = random.nextInt(loadedData.length);
|
||||
offsets[i] = offset;
|
||||
results[i] = executor.submit(new Callable<Response>() {
|
||||
|
||||
@Override
|
||||
public Response call() throws Exception {
|
||||
try {
|
||||
startLatch.await();
|
||||
String partialRequest = "GET /" + url + " HTTP/1.1\nRange: bytes=" + offset + "-";
|
||||
return processRequest(proxyCache, partialRequest);
|
||||
} finally {
|
||||
finishLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
startLatch.countDown();
|
||||
finishLatch.await();
|
||||
|
||||
for (int i = 0; i < results.length; i++) {
|
||||
Response response = (Response) results[i].get();
|
||||
int offset = offsets[i];
|
||||
byte[] partialData = new byte[loadedData.length - offset];
|
||||
System.arraycopy(loadedData, offset, partialData, 0, partialData.length);
|
||||
assertThat(response.data).isEqualTo(partialData);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadEmptyFile() throws Exception {
|
||||
String zeroSizeUrl = "https://raw.githubusercontent.com/danikula/AndroidVideoCache/master/files/empty.txt";
|
||||
@@ -95,4 +158,34 @@ public class HttpProxyCacheTest {
|
||||
Mockito.verify(listener).onCacheAvailable(Mockito.<File>any(), eq(zeroSizeUrl), eq(100));
|
||||
assertThat(response.data).isEmpty();
|
||||
}
|
||||
|
||||
private Response processRequest(String sourceUrl, String httpRequest) throws ProxyCacheException, IOException {
|
||||
FileCache fileCache = new FileCache(ProxyCacheTestUtils.newCacheFile());
|
||||
return processRequest(sourceUrl, httpRequest, fileCache);
|
||||
}
|
||||
|
||||
private Response processRequest(String sourceUrl, String httpRequest, FileCache fileCache) throws ProxyCacheException, IOException {
|
||||
HttpUrlSource source = new HttpUrlSource(sourceUrl);
|
||||
HttpProxyCache proxyCache = new HttpProxyCache(source, fileCache);
|
||||
return processRequest(proxyCache, httpRequest);
|
||||
}
|
||||
|
||||
private Response processRequest(HttpProxyCache proxyCache, String httpRequest) throws ProxyCacheException, IOException {
|
||||
GetRequest request = new GetRequest(httpRequest);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Socket socket = mock(Socket.class);
|
||||
when(socket.getOutputStream()).thenReturn(out);
|
||||
proxyCache.processRequest(request, socket);
|
||||
return new Response(out.toByteArray());
|
||||
}
|
||||
|
||||
private Future<Response> processAsync(ExecutorService executor, final HttpProxyCache proxyCache, final String httpRequest) {
|
||||
return executor.submit(new Callable<Response>() {
|
||||
|
||||
@Override
|
||||
public Response call() throws Exception {
|
||||
return processRequest(proxyCache, httpRequest);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user