blob: 974218b1f7454f10b3f9f97c8f42d216d30ffc74 [file] [log] [blame]
/*
* Copyright (C) 2014 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static okio.TestUtil.bufferWithRandomSegmentLayout;
import static org.junit.Assert.*;
/**
* This test uses four timeouts of varying durations: 250ms, 500ms, 750ms and
* 1000ms, named 'a', 'b', 'c' and 'd'.
*/
public final class AsyncTimeoutTest {
private final BlockingDeque<AsyncTimeout> timedOut = new LinkedBlockingDeque<>();
private final AsyncTimeout a = new RecordingAsyncTimeout();
private final AsyncTimeout b = new RecordingAsyncTimeout();
private final AsyncTimeout c = new RecordingAsyncTimeout();
private final AsyncTimeout d = new RecordingAsyncTimeout();
@Before public void setUp() throws Exception {
a.timeout( 250, TimeUnit.MILLISECONDS);
b.timeout( 500, TimeUnit.MILLISECONDS);
c.timeout( 750, TimeUnit.MILLISECONDS);
d.timeout(1000, TimeUnit.MILLISECONDS);
}
@Test public void zeroTimeoutIsNoTimeout() throws Exception {
AsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.timeout(0, TimeUnit.MILLISECONDS);
timeout.enter();
Thread.sleep(250);
assertFalse(timeout.exit());
assertTimedOut();
}
@Test public void singleInstanceTimedOut() throws Exception {
a.enter();
Thread.sleep(500);
assertTrue(a.exit());
assertTimedOut(a);
}
@Test public void singleInstanceNotTimedOut() throws Exception {
b.enter();
Thread.sleep(250);
b.exit();
assertFalse(b.exit());
assertTimedOut();
}
@Test public void instancesAddedAtEnd() throws Exception {
a.enter();
b.enter();
c.enter();
d.enter();
Thread.sleep(1250);
assertTrue(a.exit());
assertTrue(b.exit());
assertTrue(c.exit());
assertTrue(d.exit());
assertTimedOut(a, b, c, d);
}
@Test public void instancesAddedAtFront() throws Exception {
d.enter();
c.enter();
b.enter();
a.enter();
Thread.sleep(1250);
assertTrue(d.exit());
assertTrue(c.exit());
assertTrue(b.exit());
assertTrue(a.exit());
assertTimedOut(a, b, c, d);
}
@Test public void instancesRemovedAtFront() throws Exception {
a.enter();
b.enter();
c.enter();
d.enter();
assertFalse(a.exit());
assertFalse(b.exit());
assertFalse(c.exit());
assertFalse(d.exit());
assertTimedOut();
}
@Test public void instancesRemovedAtEnd() throws Exception {
a.enter();
b.enter();
c.enter();
d.enter();
assertFalse(d.exit());
assertFalse(c.exit());
assertFalse(b.exit());
assertFalse(a.exit());
assertTimedOut();
}
@Test public void doubleEnter() throws Exception {
a.enter();
try {
a.enter();
fail();
} catch (IllegalStateException expected) {
}
}
@Test public void reEnter() throws Exception {
a.timeout(10, SECONDS);
a.enter();
assertFalse(a.exit());
a.enter();
assertFalse(a.exit());
}
@Test public void reEnterAfterTimeout() throws Exception {
a.timeout(1, MILLISECONDS);
a.enter();
assertSame(a, timedOut.take());
assertTrue(a.exit());
a.enter();
assertFalse(a.exit());
}
@Test public void deadlineOnly() throws Exception {
RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.deadline(250, TimeUnit.MILLISECONDS);
timeout.enter();
Thread.sleep(500);
assertTrue(timeout.exit());
assertTimedOut(timeout);
}
@Test public void deadlineBeforeTimeout() throws Exception {
RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.deadline(250, TimeUnit.MILLISECONDS);
timeout.timeout(750, TimeUnit.MILLISECONDS);
timeout.enter();
Thread.sleep(500);
assertTrue(timeout.exit());
assertTimedOut(timeout);
}
@Test public void deadlineAfterTimeout() throws Exception {
RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
timeout.deadline(750, TimeUnit.MILLISECONDS);
timeout.enter();
Thread.sleep(500);
assertTrue(timeout.exit());
assertTimedOut(timeout);
}
@Test public void deadlineStartsBeforeEnter() throws Exception {
RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.deadline(500, TimeUnit.MILLISECONDS);
Thread.sleep(500);
timeout.enter();
Thread.sleep(250);
assertTrue(timeout.exit());
assertTimedOut(timeout);
}
@Test public void deadlineInThePast() throws Exception {
RecordingAsyncTimeout timeout = new RecordingAsyncTimeout();
timeout.deadlineNanoTime(System.nanoTime() - 1);
timeout.enter();
Thread.sleep(250);
assertTrue(timeout.exit());
assertTimedOut(timeout);
}
@Test public void wrappedSinkTimesOut() throws Exception {
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void write(Buffer source, long byteCount) throws IOException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
Buffer data = new Buffer().writeUtf8("a");
try {
timeoutSink.write(data, 1);
fail();
} catch (InterruptedIOException expected) {
}
}
@Test public void wrappedSinkFlushTimesOut() throws Exception {
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void flush() throws IOException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
try {
timeoutSink.flush();
fail();
} catch (InterruptedIOException expected) {
}
}
@Test public void wrappedSinkCloseTimesOut() throws Exception {
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void close() throws IOException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
try {
timeoutSink.close();
fail();
} catch (InterruptedIOException expected) {
}
}
@Test public void wrappedSourceTimesOut() throws Exception {
Source source = new ForwardingSource(new Buffer()) {
@Override public long read(Buffer sink, long byteCount) throws IOException {
try {
Thread.sleep(500);
return -1;
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Source timeoutSource = timeout.source(source);
try {
timeoutSource.read(new Buffer(), 0);
fail();
} catch (InterruptedIOException expected) {
}
}
@Test public void wrappedSourceCloseTimesOut() throws Exception {
Source source = new ForwardingSource(new Buffer()) {
@Override public void close() throws IOException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Source timeoutSource = timeout.source(source);
try {
timeoutSource.close();
fail();
} catch (InterruptedIOException expected) {
}
}
@Test public void wrappedThrowsWithTimeout() throws Exception {
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void write(Buffer source, long byteCount) throws IOException {
try {
Thread.sleep(500);
throw new IOException("exception and timeout");
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
Buffer data = new Buffer().writeUtf8("a");
try {
timeoutSink.write(data, 1);
fail();
} catch (InterruptedIOException expected) {
assertEquals("timeout", expected.getMessage());
assertEquals("exception and timeout", expected.getCause().getMessage());
}
}
@Test public void wrappedThrowsWithoutTimeout() throws Exception {
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void write(Buffer source, long byteCount) throws IOException {
throw new IOException("no timeout occurred");
}
};
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
Buffer data = new Buffer().writeUtf8("a");
try {
timeoutSink.write(data, 1);
fail();
} catch (IOException expected) {
assertEquals("no timeout occurred", expected.getMessage());
}
}
/**
* We had a bug where writing a very large buffer would fail with an
* unexpected timeout because although the sink was making steady forward
* progress, doing it all as a single write caused a timeout.
*/
@Ignore("Flaky")
@Test public void sinkSplitsLargeWrites() throws Exception {
byte[] data = new byte[512 * 1024];
Random dice = new Random(0);
dice.nextBytes(data);
final Buffer source = bufferWithRandomSegmentLayout(dice, data);
final Buffer target = new Buffer();
Sink sink = new ForwardingSink(new Buffer()) {
@Override public void write(Buffer source, long byteCount) throws IOException {
try {
Thread.sleep(byteCount / 500); // ~500 KiB/s.
target.write(source, byteCount);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
// Timeout after 250 ms of inactivity.
AsyncTimeout timeout = new AsyncTimeout();
timeout.timeout(250, TimeUnit.MILLISECONDS);
Sink timeoutSink = timeout.sink(sink);
// Transmit 500 KiB of data, which should take ~1 second. But expect no timeout!
timeoutSink.write(source, source.size());
// The data should all have arrived.
assertEquals(ByteString.of(data), target.readByteString());
}
/** Asserts which timeouts fired, and in which order. */
private void assertTimedOut(Timeout... expected) {
assertEquals(Arrays.asList(expected), new ArrayList<Timeout>(timedOut));
}
class RecordingAsyncTimeout extends AsyncTimeout {
@Override protected void timedOut() {
timedOut.add(this);
}
}
}