-
Notifications
You must be signed in to change notification settings - Fork 25.6k
refactor onStart and onFinish to take runnables and executed them guarded by state #40855
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
aaa7338
7d6d530
3a69a1e
f8d9ca3
8038608
673150b
ceb6948
088cafc
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 |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ | |
| /** | ||
| * An abstract class that builds an index incrementally. A background job can be launched using {@link #maybeTriggerAsyncJob(long)}, | ||
| * it will create the index from the source index up to the last complete bucket that is allowed to be built (based on job position). | ||
| * Only one background job can run simultaneously and {@link #onFinish()} is called when the job | ||
| * Only one background job can run simultaneously and {@link #onFinish(Runnable)} is called when the job | ||
| * finishes. {@link #onFailure(Exception)} is called if the job fails with an exception and {@link #onAbort()} is called if the indexer is | ||
| * aborted while a job is running. The indexer must be started ({@link #start()} to allow a background job to run when | ||
| * {@link #maybeTriggerAsyncJob(long)} is called. {@link #stop()} can be used to stop the background job without aborting the indexer. | ||
|
|
@@ -85,13 +85,10 @@ public synchronized IndexerState start() { | |
|
|
||
| /** | ||
| * Sets the internal state to {@link IndexerState#STOPPING} if an async job is | ||
| * running in the background and in such case {@link #onFinish()} will be called | ||
| * as soon as the background job detects that the indexer is stopped. If there | ||
| * is no job running when this function is called, the state is directly set to | ||
| * {@link IndexerState#STOPPED} and {@link #onFinish()} will never be called. | ||
| * running in the background. If there is no job running when this function is | ||
| * called, the state is directly set to {@link IndexerState#STOPPED}. | ||
| * | ||
| * @return The new state for the indexer (STOPPED, STOPPING or ABORTING if the | ||
| * job was already aborted). | ||
| * @return The new state for the indexer (STOPPED, STOPPING or ABORTING if the job was already aborted). | ||
| */ | ||
| public synchronized IndexerState stop() { | ||
| IndexerState currentState = state.updateAndGet(previousState -> { | ||
|
|
@@ -148,16 +145,18 @@ public synchronized boolean maybeTriggerAsyncJob(long now) { | |
| case STARTED: | ||
| logger.debug("Schedule was triggered for job [" + getJobId() + "], state: [" + currentState + "]"); | ||
| stats.incrementNumInvocations(1); | ||
| onStartJob(now); | ||
|
|
||
| if (state.compareAndSet(IndexerState.STARTED, IndexerState.INDEXING)) { | ||
| // fire off the search. Note this is async, the method will return from here | ||
| executor.execute(() -> { | ||
| try { | ||
| stats.markStartSearch(); | ||
| doNextSearch(buildSearchRequest(), ActionListener.wrap(this::onSearchResponse, this::finishWithSearchFailure)); | ||
| onStart(now, () -> { | ||
| stats.markStartSearch(); | ||
| doNextSearch(buildSearchRequest(), ActionListener.wrap(this::onSearchResponse, this::finishWithSearchFailure)); | ||
| }); | ||
| } catch (Exception e) { | ||
| finishWithSearchFailure(e); | ||
| logger.error("Indexer failed on start", e); | ||
| doSaveState(finishAndSetState(), position.get(), () -> onFailure(e)); | ||
| } | ||
| }); | ||
| logger.debug("Beginning to index [" + getJobId() + "], state: [" + currentState + "]"); | ||
|
|
@@ -199,9 +198,12 @@ public synchronized boolean maybeTriggerAsyncJob(long now) { | |
| * Called at startup after job has been triggered using {@link #maybeTriggerAsyncJob(long)} and the | ||
| * internal state is {@link IndexerState#STARTED}. | ||
| * | ||
| * Implementors MUST ensure that next.run() gets called. | ||
| * | ||
| * @param now The current time in milliseconds passed through from {@link #maybeTriggerAsyncJob(long)} | ||
| * @param next Runnable for the next phase | ||
| */ | ||
| protected abstract void onStartJob(long now); | ||
| protected abstract void onStart(long now, Runnable next); | ||
|
||
|
|
||
| /** | ||
| * Executes the {@link SearchRequest} and calls <code>nextPhase</code> with the | ||
|
|
@@ -248,9 +250,13 @@ public synchronized boolean maybeTriggerAsyncJob(long now) { | |
| protected abstract void onFailure(Exception exc); | ||
|
|
||
| /** | ||
| * Called when a background job finishes. | ||
| * Called when a background job finishes before the internal state changes from {@link IndexerState#INDEXING} back to | ||
| * {@link IndexerState#STARTED}. The passed runnable trigger persisting and changing the state and can be wrapped to | ||
| * execute code after the state has changed. | ||
| * | ||
| * @param finishAndSetState Runnable for finishing and setting state | ||
| */ | ||
| protected abstract void onFinish(); | ||
| protected abstract void onFinish(Runnable finishAndSetState); | ||
|
|
||
| /** | ||
| * Called when a background job detects that the indexer is aborted causing the | ||
|
|
@@ -315,10 +321,19 @@ private void onSearchResponse(SearchResponse searchResponse) { | |
| if (iterationResult.isDone()) { | ||
| logger.debug("Finished indexing for job [" + getJobId() + "], saving state and shutting down."); | ||
|
|
||
| // Change state first, then try to persist. This prevents in-progress | ||
| // STOPPING/ABORTING from | ||
| // being persisted as STARTED but then stop the job | ||
| doSaveState(finishAndSetState(), position.get(), this::onFinish); | ||
| // execute finishing tasks | ||
| try { | ||
| onFinish(() -> { | ||
| // Change state first, then try to persist. This prevents in-progress | ||
| // STOPPING/ABORTING from | ||
| // being persisted as STARTED but then stop the job | ||
| doSaveState(finishAndSetState(), position.get(), () -> {}); | ||
| }); | ||
| } catch (Exception e) { | ||
|
||
| logger.error("Indexer failed on finish", e); | ||
| doSaveState(finishAndSetState(), position.get(), () -> onFailure(e)); | ||
| } | ||
|
|
||
| return; | ||
| } | ||
|
|
||
|
|
@@ -337,6 +352,8 @@ private void onSearchResponse(SearchResponse searchResponse) { | |
| logger.warn("Error while attempting to bulk index documents: " + bulkResponse.buildFailureMessage()); | ||
| } | ||
| stats.incrementNumOutputDocuments(bulkResponse.getItems().length); | ||
|
|
||
| // check if indexer has been asked to stop, state {@link IndexerState#STOPPING} | ||
| if (checkState(getState()) == false) { | ||
| 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.
I don't think we should catch the exception here, if an error occurs in
onStartwe should provide an ActionListener to let the caller deals with the error (e.g. call listener.onFailure(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.
I also wonder if we should call onFailure before we save the state to be consistent with onFinish :
onFailure(Exception e, Runnable finishAndSaveState)?