-
Notifications
You must be signed in to change notification settings - Fork 10
Conversation
…x in partial result holder
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.
Thanks Surya few comments from the first pass, will take a detailed look
BytesReference bytesReference = | ||
BytesReference.fromByteBuffer(ByteBuffer.wrap(Base64.getUrlDecoder().decode(asyncSearchPersistenceModel.getResponse()))); | ||
try { | ||
NamedWriteableAwareStreamInput wrapperStreamInput = new NamedWriteableAwareStreamInput(bytesReference.streamInput(), | ||
namedWriteableRegistry); | ||
SearchResponse asyncSearchResponse = new SearchResponse(wrapperStreamInput); | ||
wrapperStreamInput.close(); | ||
return asyncSearchResponse; | ||
} catch (IOException e) { | ||
logger.error("Failed to parse search response " + asyncSearchPersistenceModel.getResponse(), e); | ||
return null; |
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.
Use try-with-resource to avoid resource leaks
try { | ||
NamedWriteableAwareStreamInput wrapperStreamInput = new NamedWriteableAwareStreamInput(bytesReference.streamInput(), | ||
namedWriteableRegistry); | ||
ElasticsearchException exception =wrapperStreamInput.readException(); | ||
wrapperStreamInput.close(); | ||
return exception; | ||
} catch (IOException e) { | ||
logger.error("Failed to parse search error " + asyncSearchPersistenceModel.getError(), e); | ||
return null; |
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.
Try with resource here as well
(s, e) -> {}, (contextId, listener) -> listener.onContextPersisted(contextId), SearchResponsePersistedEvent.class)); | ||
(s, e) -> { | ||
}, (contextId, listener) -> listener.onContextPersisted(contextId), SearchResponsePersistedEvent.class)); | ||
|
||
stateMachine.registerTransition(new AsyncSearchTransition<>(FAILED, PERSISTED, | ||
(s, e) -> {}, (contextId, listener) -> listener.onContextPersisted(contextId), SearchResponsePersistedEvent.class)); | ||
(s, e) -> { | ||
}, (contextId, listener) -> listener.onContextPersisted(contextId), SearchResponsePersistedEvent.class)); | ||
|
||
//persist failed | ||
stateMachine.registerTransition(new AsyncSearchTransition<>(SUCCEEDED, PERSIST_FAILED, | ||
(s, e) -> {}, (contextId, listener) -> listener.onContextPersistFailed(contextId), SearchResponsePersistFailedEvent.class)); | ||
(s, e) -> { | ||
}, (contextId, listener) -> listener.onContextPersistFailed(contextId), SearchResponsePersistFailedEvent.class)); | ||
|
||
stateMachine.registerTransition(new AsyncSearchTransition<>(FAILED, PERSIST_FAILED, | ||
(s, e) -> {}, (contextId, listener) -> listener.onContextPersistFailed(contextId), SearchResponsePersistFailedEvent.class)); | ||
(s, e) -> { | ||
}, (contextId, listener) -> listener.onContextPersistFailed(contextId), SearchResponsePersistFailedEvent.class)); |
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.
Lets addd a TODO to populate respective EVents here
if (asyncSearchContext.getAsyncSearchState() == AsyncSearchState.DELETED) { | ||
logger.debug("Async search context {} has been moved to DELETED while waiting to acquire permits for post " + | ||
"processing", asyncSearchContext.getAsyncSearchId()); | ||
return; | ||
} |
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.
Lets release a permit here
return; | ||
} | ||
asyncSearchPersistenceService.storeResponse(asyncSearchContext.getAsyncSearchId(), | ||
persistenceModel, ActionListener.wrap( |
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.
ActionListener.runafter(ActionListener.wrap(..., releasable::close)
instead
protected void doRun() { | ||
//should we look at the task status and retry on forwarding the request if the search is still RUNNING based | ||
//on task status | ||
ClusterState state = observer.setAndGetObservedState(); |
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.
Lets skip this for now
//import static org.mockito.Mockito.mock; | ||
//import static org.mockito.Mockito.when; | ||
// | ||
//public class AsyncSearchServiceTests extends ESTestCase { |
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.
Why is it commented?
…ds of search response elements
…ticsearch/asynchronous-search into async_search_service
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.
Please address some comments i have left
|
||
@Override | ||
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) { | ||
return Collections.singletonList(new SystemIndexDescriptor(".opendistro_asynchronous_search_response", | ||
return Collections.singletonList(new SystemIndexDescriptor(".opendistro_asynchroAnous_search_response", |
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.
Typo
} | ||
//free async search context if one exists | ||
groupedDeletionListener.onResponse(asyncSearchActiveStore.freeContext(asyncSearchContextId)); |
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.
Lets add an else case to avoid doubly cleaning up
for (AsyncSearchActiveContext asyncSearchActiveContext : asyncSearchActiveStore.getAllContexts().values()) { | ||
AsyncSearchState stage = asyncSearchActiveContext.getAsyncSearchState(); | ||
if (stage != null && ( | ||
!asyncSearchActiveContext.retainedStages().contains(stage) || asyncSearchActiveContext.isExpired())) { |
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.
Replace ! with ==false
} | ||
} | ||
} catch (Exception e) { | ||
logger.debug("Exception while reaping async search active contexts", e); |
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.
Lets improve logging, try adding async search id for every log we have for better debuggability
public boolean freeContext(AsyncSearchContextId asyncSearchContextId) { | ||
AsyncSearchActiveContext asyncSearchContext = activeContexts.get(asyncSearchContextId.getId()); | ||
if (asyncSearchContext != null) { | ||
logger.debug("Removing {} from context map", asyncSearchContextId); | ||
activeContexts.remove(asyncSearchContextId.getId()); | ||
return true; | ||
} | ||
return false; | ||
} |
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.
This part needs to be sync ed up with the lastest from context isAlive
protected void metadataToXContent(XContentBuilder builder, Params params) throws IOException { | ||
builder.field("limit", limit); | ||
} |
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.
where is it being used
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.
listener.onFailure(exp); | ||
} | ||
// handle request locally if we were not able to forward the request | ||
handleRequest(asyncSearchId, request, listener); | ||
} | ||
}); | ||
} else { | ||
handleRequest(asyncSearchId, request, listener); | ||
} |
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.
Lets handle the exception only if we are unable to forward
|
||
protected FetchAsyncSearchRequest(StreamInput in) throws IOException { | ||
super(in); | ||
connectionTimeout = in.readTimeValue(); |
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.
Is this a required field? I think you should have a default timeout and make this optional?
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.
Agreed. Making it optional.
/** | ||
* A request to delete an async search response based on its id | ||
*/ | ||
public class DeleteAsyncSearchRequest extends FetchAsyncSearchRequest<DeleteAsyncSearchRequest> { |
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.
Looks like a wierd extension name- maybe have a AsyncSearchRequest extended by both Delete and Fetch- e.g. what if the fetch request includes more params in future like pagination etc. How would that fit in with Delete?
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.
Renaming it to AsyncSearchRoutingRequest.
/** | ||
* A request to fetch an async search response by id. | ||
*/ | ||
public class GetAsyncSearchRequest extends FetchAsyncSearchRequest<GetAsyncSearchRequest> { |
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.
It is not clear how fetch and get are different just by looking at the class names?
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.
Renaming it to AsyncSearchRoutingRequest.
this.searchRequest.setCcsMinimizeRoundtrips(CCR_MINIMIZE_ROUNDTRIPS); | ||
this.searchRequest.setBatchedReduceSize(DEFAULT_BATCHED_REDUCE_SIZE); | ||
this.searchRequest.setPreFilterShardSize(DEFAULT_PRE_FILTER_SHARD_SIZE); | ||
this.searchRequest.requestCache(DEFAULT_REQUEST_CACHE); |
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.
What if these params are already set in SearchRequest? Do you intend to override these params for async search?
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.
Not all cases. Yes this needs a fix, we need to check values and only selectively override
public SubmitAsyncSearchRequest(StreamInput in) throws IOException { | ||
super(in); | ||
this.searchRequest = new SearchRequest(in); | ||
this.waitForCompletionTimeout = in.readTimeValue(); |
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.
isn't this optional?
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.
Agreed. Making it optional.
asyncSearchContext.getAsyncSearchId(), response); | ||
//Intent of the lock here is to disallow ongoing migration to system index | ||
// as if that is underway we might end up creating a new document post a DELETE was executed | ||
asyncSearchContext.acquireContextPermit(ActionListener.wrap( | ||
releasable -> { | ||
//free context marks the context as DELETED | ||
groupedDeletionListener.onResponse( | ||
asyncSearchActiveStore.freeContext(asyncSearchContextId)); | ||
releasable.close(); | ||
}, listener::onFailure), TimeValue.timeValueSeconds(5), "free context"); | ||
}, | ||
(e) -> { | ||
asyncSearchContext.acquireContextPermit(ActionListener.wrap( | ||
releasable -> { | ||
groupedDeletionListener.onResponse( | ||
asyncSearchActiveStore.freeContext(asyncSearchContextId)); | ||
releasable.close(); | ||
//TODO introduce request timeouts to make the permit wait transparent to the client | ||
}, listener::onFailure), TimeValue.timeValueSeconds(5), "free context"); | ||
logger.debug(() -> new ParameterizedMessage("Unable to cancel async search task {}", | ||
asyncSearchContext.getTask()), e); |
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.
lot of duplicate code?
asyncSearchStateMachine.trigger(new SearchSuccessfulEvent(asyncSearchContext, searchResponse)); | ||
if (asyncSearchContext.shouldPersist()) { | ||
asyncSearchStateMachine.trigger(new BeginPersistEvent(asyncSearchContext)); | ||
asyncSearchActiveStore.freeContext(asyncSearchContext.getContextId()); |
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.
persist event execution frees the context. Why do you need to do this twice?
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.
Agreed. this has been removed.
asyncSearchContext.acquireAllContextPermits(ActionListener.wrap(releasable -> { | ||
// check again after acquiring permit if the context has been deleted mean while | ||
if (asyncSearchContext.shouldPersist() == false) { | ||
logger.debug("Async search context {} has been moved to DELETED while waiting to acquire permits for post " + |
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.
no DELETED state as per your new logic?
asyncSearchStateMachine.trigger(new SearchFailureEvent(asyncSearchContext, exception)); | ||
if (asyncSearchContext.shouldPersist()) { | ||
asyncSearchStateMachine.trigger(new BeginPersistEvent(asyncSearchContext)); | ||
asyncSearchActiveStore.freeContext(asyncSearchContext.getContextId()); |
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.
Why freeing the context here?
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.
Agreed. this has been removed.
asyncSearchPersistenceService.storeResponse(asyncSearchContext.getAsyncSearchId(), | ||
persistenceModel, ActionListener.runAfter(ActionListener.wrap( | ||
(indexResponse) -> { | ||
//Mark any dangling reference as PERSISTED and cleaning it up from the IN_MEMORY context |
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.
Why do you call it dangling?
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.
Concurrent get or delete requests may be holding reference to the active context.
…ticsearch/asynchronous-search into async_search_service
…ticsearch/asynchronous-search into async_search_service
…ticsearch/asynchronous-search into async_search_service
} | ||
return true; | ||
} | ||
|
||
@Override | ||
public void close() { |
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.
synchronized?
@@ -59,6 +61,7 @@ | |||
private final AtomicBoolean completed; | |||
private final SetOnce<Exception> error; | |||
private final SetOnce<SearchResponse> searchResponse; | |||
private final AtomicBoolean closed; | |||
private final AsyncSearchContextPermits asyncSearchContextPermits; | |||
|
|||
public AsyncSearchActiveContext(AsyncSearchContextId asyncSearchContextId, String nodeId, |
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.
Can this be executing even after close is called? If not, then you need to check for closed flag in every method?
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.
Yes. Have added this check in every method.
} | ||
|
||
private AsyncSearchStateMachine getAsyncSearchStateMachineDefinition() { |
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.
Where has this gone now?
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.
AsyncSearchStateMachine
is being initialized in the AsyncSearchService
…Supplier to System.CurrentTimeMillis
…ource clean up in integration test.
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.
As discussed, please be careful about ResourceNotFound exception. It might break some consistency guarantees of your API for concurrent DELETE and GET calls. You might want to throw NotAvailable instead of NotFound to preserve strong consistency.
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.
Awesome job on the tests! looks pretty much in the good shape.
Issue #3
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.