Skip to content

Commit

Permalink
prepare 2.6.0 release (#70)
Browse files Browse the repository at this point in the history
* add time threshold for backoff reset

* allow endpoint to be specified as either URI or HttpUrl

* add @SInCE

* add interface for customizing requests

* javadoc fixes

* add changelog for past releases

* remove JSR305

* replace SSL-specific config method with general-purpose HTTP config method

* make helper method static

* add end-to-end EventSource tests

* spacing

* omit default header value if there's a custom value

* avoid trailing period in logger name

* add 1.x branch

* update to OkHttp 4.x and Java 8

* javadoc fixes

* remove EventSource setters, + test improvements

* update Gradle release

* enable Github Pages

* skip tests in release

* add ability to force a stream restart; improve tests so we can test this

* revert whitespace change

* add 1.x branch

* bump OkHttp version to latest Java 7-compatible version

* prepare 1.10.2 release (#43)

* Releasing version 1.10.2

* Gradle update + build fixes

* add ability to force a stream restart; improve tests so we can test this (#29)

* prepare 1.11.0 release (#44)

* Releasing version 1.11.0

* Gradle release fixes

* update to okhttp 4.5.0

* longer timeout for cleaner shutdown of test servers

* fix Gradle scopes

* allow setting specific thread priority

* remove misleading logging & unnecessary backoff, improve tests (#34)

* known issue with onClose() - add comment, disable test assertions

* allow caller to specify a custom logger instead of SLF4J (#32)

* add method for changing base name of SLF4J logger

* enable coverage reports in CI, improve CI to test all supported Java versions

* rm inapplicable CI copy-paste

* another CI fix

* add checkstyle config

* fix jitter calculation when upper bound is a power of 2

* prepare 1.11.1 release (#49)

* Releasing version 1.11.1

* misc coverage + test improvements, add CI enforcement of coverage (#39)

* fix shutdown state logic, simplify code paths (#40)

* Fix Java 7 compatibility.

* add OpenJDK 7 build + fix test race condition + javadoc fix (#42)

* prepare 1.11.2 release (#56)

* Releasing version 1.11.2

* update Gradle to 6.8.3

* Kotlinize build script

* fix logic for shutting down after an unrecoverable error

* use newer HTTP test helpers

* use Releaser v2 config + newer CI images (#47)

* use new stream-reading implementation to support CR-only line endings

* make buffer size configurable

* rm usage that's not allowed in Java 8

* add Guava test dependency

* add code coverage ovverride

* implement contract tests (#48)

* use Gradle 7

* Bounded queues for the EventHandler thread (#58)

* Bounded queue for the EventHandler thread

The unbounded queue fronting the 'event' thread can cause trouble when the
EventHandler is unable to keep up with the workload. This can lead to heap
exhaustion, GC issues and failure modes that are generally considered "bad".

Band-aid over this with a semaphore to limit the number of tasks in the queue.
The semaphore is opt-in and disabled by default to avoid any nasty surprises
for folks upgrading.

Also add 'EventSource.awaitClosed()' to allow users to wait for underlying
thread pools to completely shut down. We can't know if it's safe to clean
up resources used by the EventHandler thread if we can't be certain that it
has completely terminated.

* Address checkstyle griping in StubServer

* Fix JavaDoc issue

* Tighten up exception handling

Co-authored-by: Eli Bishop <eli@launchdarkly.com>

* update @SInCE

* test Java 17 in CI (#51)

* improve tests for AsyncEventHandler and EventSource.awaitClosed (#52)

* add streaming data mode for very large events (#53)

* add option to ensure that expected fields are always read

Co-authored-by: Eli Bishop <eli@launchdarkly.com>
Co-authored-by: LaunchDarklyCI <dev@launchdarkly.com>
Co-authored-by: LaunchDarklyCI <LaunchDarklyCI@users.noreply.github.com>
Co-authored-by: Gavin Whelan <gwhelan@launchdarkly.com>
Co-authored-by: LaunchDarklyReleaseBot <launchdarklyreleasebot@launchdarkly.com>
Co-authored-by: Tom Lee <93216+thomaslee@users.noreply.github.com>
  • Loading branch information
7 people authored Jun 29, 2022
1 parent 9cec12a commit 7ed11cf
Show file tree
Hide file tree
Showing 18 changed files with 1,419 additions and 450 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,13 @@ tasks.jacocoTestCoverageVerification.configure {
// The key for each of these items is the complete method signature minus the "com.launchdarkly.eventsource." prefix.
"AsyncEventHandler.acquire()" to 2,
"AsyncEventHandler.execute(java.lang.Runnable)" to 3,
"BufferedUtf8LineReader.getLineFromBuffer()" to 2,
"EventSource.awaitClosed(java.time.Duration)" to 3,
"EventSource.handleSuccessfulResponse(okhttp3.Response)" to 2,
"EventSource.maybeReconnectDelay(int, long)" to 2,
"EventSource.run()" to 3,
"EventSource.Builder.createInitialClientBuilder()" to 1,
"EventSource.Builder.defaultTrustManager()" to 2,
"MessageEvent.getData()" to 2,
"SLF4JLogger.error(java.lang.String)" to 2,
"ModernTLSSocketFactory.createSocket(java.lang.String, int)" to 1,
"ModernTLSSocketFactory.createSocket(java.lang.String, int, java.net.InetAddress, int)" to 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* <p>
* This class guarantees that runtime exceptions are never thrown back to the EventSource.
*/
class AsyncEventHandler implements EventHandler {
final class AsyncEventHandler implements EventHandler {
private final Executor executor;
private final EventHandler eventSourceHandler;
private final Logger logger;
Expand Down Expand Up @@ -62,6 +62,8 @@ public void onMessage(final String event, final MessageEvent messageEvent) {
eventSourceHandler.onMessage(event, messageEvent);
} catch (Exception e) {
handleUnexpectedError(e);
} finally {
messageEvent.close();
}
});
}
Expand Down
146 changes: 146 additions & 0 deletions src/main/java/com/launchdarkly/eventsource/BufferedLineParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.launchdarkly.eventsource;

import java.io.IOException;
import java.io.InputStream;

/**
* Buffers a byte stream and returns it in chunks while scanning for line endings, which
* may be any of CR (\r), LF (\n), or CRLF. The SSE specification allows any of these line
* endings for any line in the stream.
* <p>
* To use this class, we repeatedly call {@code read()} to obtain a piece of data. Rather
* than copying this back into a buffer provided by the caller, BufferedLineParser exposes
* its own fixed-size buffer directly and marks the portion being read; the caller is
* responsible for inspecting this data before the next call to {@code read()}.
* <p>
* This class is not thread-safe.
*/
final class BufferedLineParser {
private final InputStream stream;
private final byte[] readBuffer;
private int readBufferCount = 0;
private int scanPos = 0;
private int chunkStart = 0;
private int chunkEnd = 0;
private boolean lastCharWasCr = false;
private boolean eof = false;

public BufferedLineParser(InputStream stream, int bufferSize) {
this.stream = stream;
this.readBuffer = new byte[bufferSize];
}

/**
* Returns true if we have reached the end of the stream.
*
* @return true if at end of stream
*/
public boolean isEof() {
return eof;
}

/**
* Attempts to read the next chunk. A chunk is terminated either by a line ending, or
* by reaching the end of the buffer before the next read from the underlying stream.
* After calling this method, use the getter methods to access the data in the chunk.
* <p>
* The method returns {@code true} if the chunk was terminated by a line ending, or
* {@code false} otherwise. The line ending is not included in the chunk data.
*
* @return true if the parser reached a line ending
* @throws IOException if the underlying stream threw an exception
*/
public boolean read() throws IOException {
if (scanPos > 0 && readBufferCount > scanPos) {
System.arraycopy(readBuffer, scanPos, readBuffer, 0, readBufferCount - scanPos);
}
readBufferCount -= scanPos;
scanPos = chunkStart = chunkEnd = 0;
while (true) {
if (scanPos < readBufferCount && scanForTerminator()) {
return true;
}
if (!readMoreIntoBuffer()) {
return false;
}
}
}

/**
* Returns the fixed-size buffer that all chunks are read into.
*
* @return the backing byte array
*/
public byte[] getBuffer() {
return readBuffer;
}

/**
* Returns the byte offset within the fixed-size buffer where the most recently read
* chunk begins.
*
* @return the chunk offset
*/
public int getChunkOffset() {
return chunkStart;
}

/**
* Returns the number of bytes in the most recently read chunk, not including any line ending.
*
* @return the chunk size
*/
public int getChunkSize() {
return chunkEnd - chunkStart;
}

private boolean scanForTerminator() {
if (lastCharWasCr) {
// This handles the case where the previous reads ended in CR, so we couldn't tell
// at that time whether it was just a plain CR or part of a CRLF. We know that the
// previous line has ended either way, we just need to ensure that if the next byte
// is LF, we skip it.
lastCharWasCr = false;
if (readBuffer[scanPos] == '\n') {
scanPos++;
chunkStart++;
}
}

while (scanPos < readBufferCount) {
byte b = readBuffer[scanPos];
if (b == '\n' || b == '\r') {
break;
}
scanPos++;
}
chunkEnd = scanPos;
if (scanPos == readBufferCount) {
// We haven't found a terminator yet; we'll need to read more from the stream.
return false;
}

scanPos++;
if (readBuffer[chunkEnd] == '\r') {
if (scanPos == readBufferCount) {
lastCharWasCr = true;
} else if (readBuffer[scanPos] == '\n') {
scanPos++;
}
}
return true;
}

private boolean readMoreIntoBuffer() throws IOException {
if (readBufferCount == readBuffer.length) {
return false;
}
int readCount = stream.read(readBuffer, readBufferCount, readBuffer.length - readBufferCount);
if (readCount < 0) {
eof = true;
return false; // stream was closed
}
readBufferCount += readCount;
return true;
}
}
141 changes: 0 additions & 141 deletions src/main/java/com/launchdarkly/eventsource/BufferedUtf8LineReader.java

This file was deleted.

Loading

0 comments on commit 7ed11cf

Please sign in to comment.