All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.elasticsearch.xpack.core.async.AsyncResultsService Maven / Gradle / Ivy

There is a newer version: 8.13.2
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

package org.elasticsearch.xpack.core.async;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.TriConsumer;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.tasks.TaskManager;

import java.util.Objects;

/**
 * Service that is capable of retrieving and cleaning up AsyncTasks regardless of their state. It works with the TaskManager, if a task
 * is still running and AsyncTaskIndexService if task results already stored there.
 */
public class AsyncResultsService> {
    private final Logger logger = LogManager.getLogger(AsyncResultsService.class);
    private final Class asyncTaskClass;
    private final TaskManager taskManager;
    private final ClusterService clusterService;
    private final AsyncTaskIndexService store;
    private final boolean updateInitialResultsInStore;
    private final TriConsumer, TimeValue> addCompletionListener;

    /**
     * Creates async results service
     *
     * @param store                       AsyncTaskIndexService for the response we are working with
     * @param updateInitialResultsInStore true if initial results are stored (Async Search) or false otherwise (EQL Search)
     * @param asyncTaskClass              async task class
     * @param addCompletionListener       function that registers a completion listener with the task
     * @param taskManager                 task manager
     * @param clusterService              cluster service
     */
    public AsyncResultsService(AsyncTaskIndexService store,
                               boolean updateInitialResultsInStore,
                               Class asyncTaskClass,
                               TriConsumer, TimeValue> addCompletionListener,
                               TaskManager taskManager,
                               ClusterService clusterService) {
        this.updateInitialResultsInStore = updateInitialResultsInStore;
        this.asyncTaskClass = asyncTaskClass;
        this.addCompletionListener = addCompletionListener;
        this.taskManager = taskManager;
        this.clusterService = clusterService;
        this.store = store;

    }

    public DiscoveryNode getNode(String id) {
        AsyncExecutionId searchId = AsyncExecutionId.decode(id);
        return clusterService.state().nodes().get(searchId.getTaskId().getNodeId());
    }

    public boolean isLocalNode(DiscoveryNode node) {
        return Objects.requireNonNull(node).equals(clusterService.localNode());
    }

    public void retrieveResult(GetAsyncResultRequest request, ActionListener listener) {
        try {
            long nowInMillis = System.currentTimeMillis();
            AsyncExecutionId searchId = AsyncExecutionId.decode(request.getId());
            long expirationTime;
            if (request.getKeepAlive() != null && request.getKeepAlive().getMillis() > 0) {
                expirationTime = nowInMillis + request.getKeepAlive().getMillis();
            } else {
                expirationTime = -1;
            }
            // EQL doesn't store initial or intermediate results so we only need to update expiration time in store for only in case of
            // async search
            if (updateInitialResultsInStore & expirationTime > 0) {
                store.updateExpirationTime(searchId.getDocId(), expirationTime,
                    ActionListener.wrap(
                        p -> getSearchResponseFromTask(searchId, request, nowInMillis, expirationTime, listener),
                        exc -> {
                            RestStatus status = ExceptionsHelper.status(ExceptionsHelper.unwrapCause(exc));
                            if (status != RestStatus.NOT_FOUND) {
                                logger.error(() -> new ParameterizedMessage("failed to update expiration time for async-search [{}]",
                                    searchId.getEncoded()), exc);
                                listener.onFailure(exc);
                            } else {
                                //the async search document or its index is not found.
                                //That can happen if an invalid/deleted search id is provided.
                                listener.onFailure(new ResourceNotFoundException(searchId.getEncoded()));
                            }
                        }
                    ));
            } else {
                getSearchResponseFromTask(searchId, request, nowInMillis, expirationTime, listener);
            }
        } catch (Exception exc) {
            listener.onFailure(exc);
        }
    }

    private void getSearchResponseFromTask(AsyncExecutionId searchId,
                                           GetAsyncResultRequest request,
                                           long nowInMillis,
                                           long expirationTimeMillis,
                                           ActionListener listener) {
        try {
            final Task task = store.getTaskAndCheckAuthentication(taskManager, searchId, asyncTaskClass);
            if (task == null) {
                getSearchResponseFromIndex(searchId, request, nowInMillis, listener);
                return;
            }

            if (task.isCancelled()) {
                listener.onFailure(new ResourceNotFoundException(searchId.getEncoded()));
                return;
            }

            if (expirationTimeMillis != -1) {
                task.setExpirationTime(expirationTimeMillis);
            }
            addCompletionListener.apply(task, new ActionListener() {
                @Override
                public void onResponse(Response response) {
                    sendFinalResponse(request, response, nowInMillis, listener);
                }

                @Override
                public void onFailure(Exception exc) {
                    listener.onFailure(exc);
                }
            }, request.getWaitForCompletionTimeout());
        } catch (Exception exc) {
            listener.onFailure(exc);
        }
    }

    private void getSearchResponseFromIndex(AsyncExecutionId searchId,
                                            GetAsyncResultRequest request,
                                            long nowInMillis,
                                            ActionListener listener) {
        store.getResponse(searchId, true,
            new ActionListener() {
                @Override
                public void onResponse(Response response) {
                    sendFinalResponse(request, response, nowInMillis, listener);
                }

                @Override
                public void onFailure(Exception e) {
                    listener.onFailure(e);
                }
            });
    }

    private void sendFinalResponse(GetAsyncResultRequest request,
                                   Response response,
                                   long nowInMillis,
                                   ActionListener listener) {
        // check if the result has expired
        if (response.getExpirationTime() < nowInMillis) {
            listener.onFailure(new ResourceNotFoundException(request.getId()));
            return;
        }

        listener.onResponse(response);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy