-
Notifications
You must be signed in to change notification settings - Fork 25.6k
[ML] Adding asynchronous start up logic for the inference API internals #135462
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
Changes from 4 commits
5833e3e
c77289e
ac3f29c
e3c9103
de0251e
a901dc6
a5d9a4e
67cd5c4
7b802ce
1c6d667
0ea22a0
ff26624
3779c49
3277d23
ef179b3
8d40454
2110e95
0eba997
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,6 +99,7 @@ protected AmazonBedrockRequestSender( | |
|
|
||
| @Override | ||
| public void startAsynchronously(ActionListener<Void> listener) { | ||
|
|
||
| throw new UnsupportedOperationException("not implemented"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be worth wrapping this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm I think in that situation we should still throw. It would be a bug if we're ever calling that method for |
||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ | |
| import org.junit.Before; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.EnumSet; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
|
|
@@ -61,6 +62,7 @@ | |
| import static org.mockito.ArgumentMatchers.anyString; | ||
| import static org.mockito.Mockito.any; | ||
| import static org.mockito.Mockito.doAnswer; | ||
| import static org.mockito.Mockito.doThrow; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
|
|
@@ -111,6 +113,91 @@ public void testCreateSender_CanCallStartMultipleTimes() throws Exception { | |
| } | ||
| } | ||
|
|
||
| public void testStart_ThrowsException_WhenAnErrorOccurs() throws IOException { | ||
| var mockManager = mock(HttpClientManager.class); | ||
| when(mockManager.getHttpClient()).thenReturn(mock(HttpClient.class)); | ||
| doThrow(new Error("failed")).when(mockManager).start(); | ||
|
|
||
| var senderFactory = new HttpRequestSender.Factory( | ||
| ServiceComponentsTests.createWithEmptySettings(threadPool), | ||
| mockManager, | ||
| mockClusterServiceEmpty() | ||
| ); | ||
|
|
||
| try (var sender = senderFactory.createSender()) { | ||
| // Checking for both exception types because there's a race condition between the Error being thrown on a separate thread | ||
| // and the startCompleted latch timing out waiting for the start to complete | ||
|
||
| var exception = expectThrowsAnyOf(List.of(Error.class, IllegalStateException.class), sender::startSynchronously); | ||
|
|
||
| if (exception instanceof Error) { | ||
| assertThat(exception.getMessage(), is("failed")); | ||
| } else { | ||
| // IllegalStateException can be thrown if the startCompleted latch times out waiting for the start to complete | ||
| assertThat(exception.getMessage(), is("Http sender startup did not complete in time")); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void testStart_ThrowsExceptionWaitingForStartToComplete() throws IOException { | ||
| var mockManager = mock(HttpClientManager.class); | ||
| when(mockManager.getHttpClient()).thenReturn(mock(HttpClient.class)); | ||
| // This won't get rethrown because it is not an Error | ||
| doThrow(new IllegalArgumentException("failed")).when(mockManager).start(); | ||
|
|
||
| var senderFactory = new HttpRequestSender.Factory( | ||
| ServiceComponentsTests.createWithEmptySettings(threadPool), | ||
| mockManager, | ||
| mockClusterServiceEmpty() | ||
| ); | ||
|
|
||
| try (var sender = senderFactory.createSender()) { | ||
| var exception = expectThrows(IllegalStateException.class, sender::startSynchronously); | ||
|
|
||
| assertThat(exception.getMessage(), is("Http sender startup did not complete in time")); | ||
| } | ||
| } | ||
|
|
||
| public void testCreateSender_CanCallStartAsyncMultipleTimes() throws Exception { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test and the one below it could be improved a little by verifying that no matter how many times we call It would also be nice if we could verify that we're calling |
||
| var asyncCalls = 3; | ||
| var senderFactory = new HttpRequestSender.Factory(createWithEmptySettings(threadPool), clientManager, mockClusterServiceEmpty()); | ||
|
|
||
| try (var sender = createSender(senderFactory)) { | ||
| var listenerList = new ArrayList<PlainActionFuture<Void>>(); | ||
|
|
||
| for (int i = 0; i < asyncCalls; i++) { | ||
| PlainActionFuture<Void> listener = new PlainActionFuture<>(); | ||
| listenerList.add(listener); | ||
| sender.startAsynchronously(listener); | ||
| } | ||
|
|
||
| for (int i = 0; i < asyncCalls; i++) { | ||
| PlainActionFuture<Void> listener = listenerList.get(i); | ||
| assertNull(listener.actionGet(TIMEOUT)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void testCreateSender_CanCallStartAsyncAndSyncMultipleTimes() throws Exception { | ||
| var asyncCalls = 3; | ||
| var senderFactory = new HttpRequestSender.Factory(createWithEmptySettings(threadPool), clientManager, mockClusterServiceEmpty()); | ||
|
|
||
| try (var sender = createSender(senderFactory)) { | ||
| var listenerList = new ArrayList<PlainActionFuture<Void>>(); | ||
|
|
||
| for (int i = 0; i < asyncCalls; i++) { | ||
| PlainActionFuture<Void> listener = new PlainActionFuture<>(); | ||
| listenerList.add(listener); | ||
| sender.startAsynchronously(listener); | ||
| sender.startSynchronously(); | ||
| } | ||
|
|
||
| for (int i = 0; i < asyncCalls; i++) { | ||
| PlainActionFuture<Void> listener = listenerList.get(i); | ||
| assertNull(listener.actionGet(TIMEOUT)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public void testCreateSender_SendsRequestAndReceivesResponse() throws Exception { | ||
| var senderFactory = new HttpRequestSender.Factory(createWithEmptySettings(threadPool), clientManager, mockClusterServiceEmpty()); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we need to do something similar for async calls, since if two async calls come in one after the other, the second one will complete immediately even if the first one hasn't finished starting the sender yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I tried to come up with a solution that would avoid having to do spin up a thread to then call the
waitForStartToCompletesince most of the time it will simply return.