From 32a2b1d8d000a19d4a340cc1d77a68973a9d65dc Mon Sep 17 00:00:00 2001 From: jwilson Date: Sun, 26 Jan 2014 11:31:23 -0500 Subject: [PATCH] OkBuffer API sketch. I'm unhappy with java.io: * No timeouts. * Every layer needs to copy bytes around. Always copying bytes. * Features like mark/reset and available() are clumsy. * Its awkard in mixed text/binary protocols like HTTP because character decoding is separate and takes over the stream. Unfortunately java.nio isn't better, just different: * It's complex. * Buffers are fixed size. * No built-in buffer pooling. * Features like mark/reset/position are clumsy. This is an obnoxious attempt at a 3rd I/O interface, mostly inspired by InputStream and OutputStream, but using growable buffers instead of byte arrays as the core data container. --- .../okhttp/mockwebserver/MockWebServer.java | 2 +- .../java/com/squareup/okhttp/Protocol.java | 2 +- .../squareup/okhttp/internal/Platform.java | 1 + .../internal/{ => bytes}/ByteString.java | 7 +- .../okhttp/internal/bytes/OkBuffer.java | 156 ++++++++++++++++++ .../okhttp/internal/bytes/Segment.java | 74 +++++++++ .../okhttp/internal/bytes/SegmentPool.java | 61 +++++++ .../squareup/okhttp/internal/bytes/Sink.java | 36 ++++ .../okhttp/internal/bytes/Source.java | 41 +++++ .../okhttp/internal/bytes/Timeout.java | 48 ++++++ .../squareup/okhttp/internal/spdy/Header.java | 2 +- .../okhttp/internal/spdy/HpackDraft05.java | 2 +- .../internal/spdy/NameValueBlockReader.java | 2 +- .../squareup/okhttp/internal/spdy/Spdy3.java | 2 +- .../okhttp/internal/ByteStringTest.java | 1 + .../okhttp/internal/bytes/OkBufferTest.java | 96 +++++++++++ .../internal/spdy/HpackDraft05Test.java | 2 +- .../java/com/squareup/okhttp/Connection.java | 2 +- .../com/squareup/okhttp/OkHttpClient.java | 2 +- .../internal/http/HttpURLConnectionImpl.java | 2 +- .../okhttp/internal/http/SpdyTransport.java | 2 +- 21 files changed, 529 insertions(+), 14 deletions(-) rename okhttp-protocols/src/main/java/com/squareup/okhttp/internal/{ => bytes}/ByteString.java (97%) create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/OkBuffer.java create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Segment.java create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/SegmentPool.java create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Sink.java create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Source.java create mode 100644 okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Timeout.java create mode 100644 okhttp-protocols/src/test/java/com/squareup/okhttp/internal/bytes/OkBufferTest.java diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java index f37fde104..4ca28bab4 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java @@ -18,7 +18,7 @@ package com.squareup.okhttp.mockwebserver; import com.squareup.okhttp.Protocol; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.NamedRunnable; import com.squareup.okhttp.internal.Platform; import com.squareup.okhttp.internal.Util; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java index f57361ee0..118fee3af 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java @@ -15,7 +15,7 @@ */ package com.squareup.okhttp; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.util.Arrays; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java index f02a2ca60..d2c3473b6 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java @@ -17,6 +17,7 @@ package com.squareup.okhttp.internal; import com.squareup.okhttp.Protocol; +import com.squareup.okhttp.internal.bytes.ByteString; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Constructor; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/ByteString.java similarity index 97% rename from okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java rename to okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/ByteString.java index 9370ebbea..64a1183a5 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/ByteString.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/ByteString.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.okhttp.internal; +package com.squareup.okhttp.internal.bytes; +import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -34,7 +35,7 @@ import static com.squareup.okhttp.internal.Util.asciiLowerCase; * process. */ public final class ByteString { - private final byte[] data; + final byte[] data; private transient int hashCode; // Lazily computed; 0 if unknown. private transient String utf8; // Lazily computed. @@ -119,7 +120,7 @@ public final class ByteString { return new ByteString(result); } - private ByteString(byte[] data) { + ByteString(byte[] data) { this.data = data; // Trusted internal constructor doesn't clone data. } diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/OkBuffer.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/OkBuffer.java new file mode 100644 index 000000000..36162bbb6 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/OkBuffer.java @@ -0,0 +1,156 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +import com.squareup.okhttp.internal.Util; +import java.io.IOException; + +/** + * A collection of bytes in memory. + * + *

Moving data from one OkBuffer to another is fast. Instead + * of copying bytes from one place in memory to another, this class just changes + * ownership of the underlying bytes. + * + *

This buffer grows with your data. Just like ArrayList, + * each OkBuffer starts small. It consumes only the memory it needs to. + * + *

This buffer pools its byte arrays. When you allocate a + * byte array in Java, the runtime must zero-fill the requested array before + * returning it to you. Even if you're going to write over that space anyway. + * This class avoids zero-fill and GC churn by pooling byte arrays. + */ +public final class OkBuffer implements Source, Sink { + private static final char[] HEX_DIGITS = + { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + private Segment segment; + private long byteCount; + + public OkBuffer() { + } + + /** Returns the number of bytes currently in this buffer. */ + public long byteCount() { + return byteCount; + } + + /** Removes {@code byteCount} bytes from this and returns them as a byte string. */ + public ByteString readByteString(int byteCount) { + return new ByteString(readBytes(byteCount)); + } + + /** Removes {@code byteCount} bytes from this, decodes them as UTF-8 and returns the string. */ + public String readUtf8(int byteCount) { + return new String(readBytes(byteCount), Util.UTF_8); + } + + private byte[] readBytes(int byteCount) { + if (byteCount > this.byteCount) { + throw new IllegalArgumentException( + String.format("requested %s > available %s", byteCount, this.byteCount)); + } + + int offset = 0; + byte[] result = new byte[byteCount]; + + while (offset < byteCount) { + int toCopy = Math.min(byteCount - offset, segment.limit - segment.pos); + System.arraycopy(segment.data, segment.pos, result, offset, toCopy); + + offset += toCopy; + segment.pos += toCopy; + + if (segment.pos == segment.limit) { + segment = segment.pop(); // Recycle this empty segment. + } + } + + this.byteCount -= byteCount; + return result; + } + + /** Appends {@code byteString} to this. */ + public void write(ByteString byteString) { + write(byteString.data); + } + + /** Encodes {@code string} as UTF-8 and appends the bytes to this. */ + public void writeUtf8(String string) { + write(string.getBytes(Util.UTF_8)); + } + + private void write(byte[] data) { + int offset = 0; + while (offset < data.length) { + if (segment == null) { + segment = SegmentPool.INSTANCE.take(); // Acquire a first segment. + segment.next = segment.prev = segment; + } + + Segment tail = segment.prev; + if (tail.limit == Segment.SIZE) { + tail = tail.push(); // Acquire a new empty segment. + } + + int toCopy = Math.min(data.length - offset, Segment.SIZE - tail.limit); + System.arraycopy(data, offset, tail.data, tail.limit, toCopy); + + offset += toCopy; + tail.limit += toCopy; + } + + this.byteCount += data.length; + } + + @Override public void write(OkBuffer source, long byteCount, Timeout timeout) { + throw new UnsupportedOperationException(); + } + + @Override public long read(OkBuffer sink, long byteCount, Timeout timeout) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override public long indexOf(byte b, Timeout timeout) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override public void flush(Timeout timeout) { + throw new UnsupportedOperationException(); + } + + @Override public void close(Timeout timeout) { + throw new UnsupportedOperationException(); + } + + /** + * Returns the contents of this buffer in hex. For buffers larger than 1 MiB + * this method is undefined. + */ + @Override public String toString() { + if (byteCount > 0x100000) return super.toString(); + char[] result = new char[(int) (byteCount * 2)]; + int offset = 0; + for (Segment s = segment; offset < byteCount; s = s.next) { + for (int i = s.pos; i < s.limit; i++) { + result[offset++] = HEX_DIGITS[(s.data[i] >> 4) & 0xf]; + result[offset++] = HEX_DIGITS[s.data[i] & 0xf]; + } + offset += s.limit - s.pos; + } + return new String(result); + } +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Segment.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Segment.java new file mode 100644 index 000000000..a1e32c348 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Segment.java @@ -0,0 +1,74 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +/** + * A segment of an OkBuffer. + * + *

Each segment in an OkBuffer is a circularly-linked list node referencing + * the following and preceding segments in the buffer. + * + *

Each segment in the pool is a singly-linked list node referencing the rest + * of segments in the pool. + */ +final class Segment { + /** The size of all segments in bytes. */ + // TODO: Using fixed-size segments makes pooling easier. But it harms memory + // efficiency and encourages copying. Try variable sized segments? + // TODO: Is 2 KiB a good default segment size? + static final int SIZE = 2048; + + final byte[] data; + int pos; + int limit; + + /** Next segment in a linked list. */ + Segment next; + + /** Previous segment in a linked list. */ + Segment prev; + + Segment() { + data = new byte[SIZE]; + } + + /** + * Removes this head of a circularly-linked list, recycles it, and returns the + * new head of the list. Returns null if the list is now empty. + */ + public Segment pop() { + Segment result = next != this ? next : null; + prev.next = next; + next.prev = prev; + next = null; + prev = null; + SegmentPool.INSTANCE.recycle(this); + return result; + } + + /** + * Acquires a segment and appends it to this tail of a circularly-linked list. + * Returns the new tail segment. + */ + public Segment push() { + Segment result = SegmentPool.INSTANCE.take(); + result.prev = this; + result.next = next; + next.prev = result; + next = result; + return result; + } +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/SegmentPool.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/SegmentPool.java new file mode 100644 index 000000000..ba9703e58 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/SegmentPool.java @@ -0,0 +1,61 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +/** + * A collection of unused segments, necessary to avoid GC churn and zero-fill. + * This pool is a thread-safe static singleton. + */ +final class SegmentPool { + static final SegmentPool INSTANCE = new SegmentPool(); + + /** The maximum number of bytes to pool. */ + // TODO: Is 64 KiB a good maximum size? Do we ever have that many idle segments? + static final long MAX_SIZE = 64 * 1024; // 64 KiB. + + /** Singly-linked list of segments. */ + private Segment next; + + /** Total bytes in this pool. */ + long byteCount; + + private SegmentPool() { + } + + Segment take() { + synchronized (this) { + if (next != null) { + Segment result = next; + next = result.next; + result.next = null; + byteCount -= Segment.SIZE; + return result; + } + } + return new Segment(); // Pool is empty. Don't zero-fill while holding a lock. + } + + void recycle(Segment segment) { + if (segment.next != null || segment.prev != null) throw new IllegalArgumentException(); + synchronized (this) { + if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full. + byteCount += Segment.SIZE; + segment.next = next; + segment.pos = segment.limit = 0; + next = segment; + } + } +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Sink.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Sink.java new file mode 100644 index 000000000..1dfca4b63 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Sink.java @@ -0,0 +1,36 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +import java.io.IOException; + +/** + * An alternative to OutputStream. + */ +public interface Sink { + /** Removes {@code byteCount} bytes from {@code source} and appends them to this. */ + void write(OkBuffer source, long byteCount, Timeout timeout) throws IOException; + + /** Pushes all buffered bytes to their final destination. */ + void flush(Timeout timeout) throws IOException; + + /** + * Pushes all buffered bytes to their final destination and releases the + * resources held by this sink. It is an error to write a closed sink. It is + * safe to close a sink more than once. + */ + void close(Timeout timeout) throws IOException; +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Source.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Source.java new file mode 100644 index 000000000..985cab8e3 --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Source.java @@ -0,0 +1,41 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +import java.io.IOException; + +/** + * An alternative to InputStream. + */ +public interface Source { + /** + * Removes {@code byteCount} bytes from this and appends them to {@code sink}. + * Returns the number of bytes actually written. + */ + long read(OkBuffer sink, long byteCount, Timeout timeout) throws IOException; + + /** + * Returns the index of {@code b} in this, or -1 if this source is exhausted + * first. This may cause this source to buffer a large number of bytes. + */ + long indexOf(byte b, Timeout timeout) throws IOException; + + /** + * Closes this source and releases the resources held by this source. It is an + * error to read a closed source. It is safe to close a source more than once. + */ + void close(Timeout timeout) throws IOException; +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Timeout.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Timeout.java new file mode 100644 index 000000000..d359c1c7a --- /dev/null +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/bytes/Timeout.java @@ -0,0 +1,48 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +import java.util.concurrent.TimeUnit; + +/** + * The deadline for a requested operation. If the timeout elapses before the + * operation has completed, the operation should be aborted. + */ +public class Timeout { + public static final Timeout NONE = new Timeout() { + @Override public Timeout start(long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override public boolean reached() { + return false; + } + }; + + private long deadlineNanos; + + public Timeout() { + } + + public Timeout start(long timeout, TimeUnit unit) { + deadlineNanos = System.nanoTime() + unit.toNanos(timeout); + return this; + } + + public boolean reached() { + return System.nanoTime() - deadlineNanos >= 0; // Subtract to avoid overflow! + } +} diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Header.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Header.java index 7b1f44b5c..45fa21318 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Header.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Header.java @@ -1,6 +1,6 @@ package com.squareup.okhttp.internal.spdy; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; /** HTTP header: the name is an ASCII string, but the value can be UTF-8. */ public final class Header { diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java index 87ba091d7..b3c069567 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java @@ -1,7 +1,7 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.internal.BitArray; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.io.InputStream; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java index 9c72eb5eb..0d6d6bf7d 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/NameValueBlockReader.java @@ -1,6 +1,6 @@ package com.squareup.okhttp.internal.spdy; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Util; import java.io.Closeable; import java.io.DataInputStream; diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java index e20c9d710..1adf02648 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java @@ -16,9 +16,9 @@ package com.squareup.okhttp.internal.spdy; import com.squareup.okhttp.Protocol; -import com.squareup.okhttp.internal.ByteString; import com.squareup.okhttp.internal.Platform; import com.squareup.okhttp.internal.Util; +import com.squareup.okhttp.internal.bytes.ByteString; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java index bf8101caf..34d37e049 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/ByteStringTest.java @@ -15,6 +15,7 @@ */ package com.squareup.okhttp.internal; +import com.squareup.okhttp.internal.bytes.ByteString; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/bytes/OkBufferTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/bytes/OkBufferTest.java new file mode 100644 index 000000000..3adbfbfb6 --- /dev/null +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/bytes/OkBufferTest.java @@ -0,0 +1,96 @@ +/* + * 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 com.squareup.okhttp.internal.bytes; + +import java.util.Arrays; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public final class OkBufferTest { + @Test public void readAndWriteUtf8() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.writeUtf8("ab"); + assertEquals(2, buffer.byteCount()); + buffer.writeUtf8("cdef"); + assertEquals(6, buffer.byteCount()); + assertEquals("abcd", buffer.readUtf8(4)); + assertEquals(2, buffer.byteCount()); + assertEquals("ef", buffer.readUtf8(2)); + assertEquals(0, buffer.byteCount()); + try { + buffer.readUtf8(1); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + @Test public void bufferToString() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.writeUtf8("\u0000\u0001\u0002\u007f"); + assertEquals("0001027f", buffer.toString()); + } + + @Test public void multipleSegmentBuffers() throws Exception { + OkBuffer buffer = new OkBuffer(); + buffer.writeUtf8(repeat('a', 1000)); + buffer.writeUtf8(repeat('b', 2500)); + buffer.writeUtf8(repeat('c', 5000)); + buffer.writeUtf8(repeat('d', 10000)); + buffer.writeUtf8(repeat('e', 25000)); + buffer.writeUtf8(repeat('f', 50000)); + + assertEquals(repeat('a', 999), buffer.readUtf8(999)); // a...a + assertEquals("a" + repeat('b', 2500) + "c", buffer.readUtf8(2502)); // ab...bc + assertEquals(repeat('c', 4998), buffer.readUtf8(4998)); // c...c + assertEquals("c" + repeat('d', 10000) + "e", buffer.readUtf8(10002)); // cd...de + assertEquals(repeat('e', 24998), buffer.readUtf8(24998)); // e...e + assertEquals("e" + repeat('f', 50000), buffer.readUtf8(50001)); // ef...f + assertEquals(0, buffer.byteCount()); + } + + @Test public void fillAndDrainPool() throws Exception { + OkBuffer buffer = new OkBuffer(); + + // Take 2 * MAX_SIZE segments. This will drain the pool, even if other tests filled it. + buffer.write(ByteString.of(new byte[(int) SegmentPool.MAX_SIZE])); + buffer.write(ByteString.of(new byte[(int) SegmentPool.MAX_SIZE])); + assertEquals(0, SegmentPool.INSTANCE.byteCount); + + // Recycle MAX_SIZE segments. They're all in the pool. + buffer.readByteString((int) SegmentPool.MAX_SIZE); + assertEquals(SegmentPool.MAX_SIZE, SegmentPool.INSTANCE.byteCount); + + // Recycle MAX_SIZE more segments. The pool is full so they get garbage collected. + buffer.readByteString((int) SegmentPool.MAX_SIZE); + assertEquals(SegmentPool.MAX_SIZE, SegmentPool.INSTANCE.byteCount); + + // Take MAX_SIZE segments to drain the pool. + buffer.write(ByteString.of(new byte[(int) SegmentPool.MAX_SIZE])); + assertEquals(0, SegmentPool.INSTANCE.byteCount); + + // Take MAX_SIZE more segments. The pool is drained so these will need to be allocated. + buffer.write(ByteString.of(new byte[(int) SegmentPool.MAX_SIZE])); + assertEquals(0, SegmentPool.INSTANCE.byteCount); + } + + private String repeat(char c, int count) { + char[] array = new char[count]; + Arrays.fill(array, c); + return new String(array); + } +} diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java index f4d470942..9e3bb1ed8 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/HpackDraft05Test.java @@ -15,7 +15,7 @@ */ package com.squareup.okhttp.internal.spdy; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index 9ff7b7069..be4eb9141 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -16,7 +16,7 @@ */ package com.squareup.okhttp; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Platform; import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.http.HttpEngine; diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index 576599762..9df597113 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -15,7 +15,7 @@ */ package com.squareup.okhttp; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.http.HttpAuthenticator; import com.squareup.okhttp.internal.http.HttpURLConnectionImpl; diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index 83a8e831c..1876772ab 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java @@ -24,7 +24,7 @@ import com.squareup.okhttp.Protocol; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; import com.squareup.okhttp.Route; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Platform; import com.squareup.okhttp.internal.Util; import java.io.FileNotFoundException; diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java index 9c1453b64..743e64b04 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java @@ -20,7 +20,7 @@ import com.squareup.okhttp.Headers; import com.squareup.okhttp.Protocol; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; -import com.squareup.okhttp.internal.ByteString; +import com.squareup.okhttp.internal.bytes.ByteString; import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.spdy.ErrorCode; import com.squareup.okhttp.internal.spdy.Header;