org.apache.solr.client.solrj.impl.BackupRequestLBHttpSolrServer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.client.solrj.impl.BackupRequestLBHttpSolrServer.java

Source

package org.apache.solr.client.solrj.impl;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.http.client.HttpClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.impl.LBHttpSolrServer;
import org.apache.solr.client.solrj.impl.LBHttpSolrServer.Req;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.StrUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BackupRequestLBHttpSolrServer extends LBHttpSolrServer {
    private static Logger log = LoggerFactory.getLogger(BackupRequestLBHttpSolrServer.class);
    private final int maximumConcurrentRequests;
    private final int backUpRequestDelay;
    private final ThreadPoolExecutor threadPoolExecuter;
    private final InflightRequestMonitor inFlightRequestMonitor;
    private final boolean tryDeadServers;

    private enum TaskState {
        ResponseReceived, ServerException, RequestException
    }

    private class RequestTaskState {
        private Exception exception;
        private TaskState stateDescription;
        private Rsp response;

        void setException(Exception exception, TaskState stateDescription) {
            this.exception = exception;
            this.stateDescription = stateDescription;
        }
    }

    public BackupRequestLBHttpSolrServer(HttpClient httpClient, ThreadPoolExecutor threadPoolExecuter,
            InflightRequestMonitor inFlightRequestMonitor, int maximumConcurrentRequests, int backUpRequestPause,
            boolean tryDeadServers) throws MalformedURLException {
        super(httpClient);
        this.threadPoolExecuter = threadPoolExecuter;
        this.maximumConcurrentRequests = maximumConcurrentRequests;
        this.backUpRequestDelay = backUpRequestPause;
        this.tryDeadServers = tryDeadServers;
        this.inFlightRequestMonitor = inFlightRequestMonitor;
    }

    /**
     * Tries to query a live server from the list provided in Req. Servers in the
     * dead pool are skipped. If a request fails due to an IOException, the server
     * is moved to the dead pool for a certain period of time, or until a test
     * request on that server succeeds.
     *
     * If a request takes longer than backUpRequestDelay the request will be sent
     * to the next server in the list, this will continue until there is a
     * response, the server list is exhausted or the number of requests in flight
     * equals maximumConcurrentRequests.
     *
     * Servers are queried in the exact order given (except servers currently in
     * the dead pool are skipped). If no live servers from the provided list
     * remain to be tried, a number of previously skipped dead servers will be
     * tried. Req.getNumDeadServersToTry() controls how many dead servers will be
     * tried.
     *
     * If no live servers are found a SolrServerException is thrown.
     *
     * @param req
     *          contains both the request as well as the list of servers to query
     *
     * @return the result of the request
     */
    @Override
    public Rsp request(Req req) throws SolrServerException, IOException {
        ArrayBlockingQueue<Future<RequestTaskState>> queue = new ArrayBlockingQueue<Future<RequestTaskState>>(
                maximumConcurrentRequests + 1);
        ExecutorCompletionService<RequestTaskState> executer = new ExecutorCompletionService<RequestTaskState>(
                threadPoolExecuter, queue);
        List<ServerWrapper> skipped = new ArrayList<ServerWrapper>(req.getNumDeadServersToTry());
        int inFlight = 0;
        RequestTaskState returnedRsp = null;
        Exception ex = null;

        for (String serverStr : req.getServers()) {
            serverStr = normalize(serverStr);
            // if the server is currently a zombie, just skip to the next one
            ServerWrapper wrapper = zombieServers.get(serverStr);
            if (wrapper != null) {
                if (tryDeadServers && skipped.size() < req.getNumDeadServersToTry()) {
                    skipped.add(wrapper);
                }
                continue;
            }
            HttpSolrServer server = makeServer(serverStr);
            Callable<RequestTaskState> task = createRequestTask(server, req, false);
            executer.submit(task);
            inFlight++;
            returnedRsp = getResponseIfReady(executer, inFlight >= maximumConcurrentRequests);
            if (returnedRsp == null) {
                // null response signifies that the response took too long.
                log.info("Server :{} did not respond before the backUpRequestDelay time of {} elapsed",
                        server.getBaseURL(), backUpRequestDelay);
                continue;
            }
            inFlight--;
            if (returnedRsp.stateDescription == TaskState.ResponseReceived) {
                return returnedRsp.response;
            } else if (returnedRsp.stateDescription == TaskState.ServerException) {
                ex = returnedRsp.exception;
            } else if (returnedRsp.stateDescription == TaskState.RequestException) {
                throw new SolrServerException(returnedRsp.exception);
            }
        }

        // no response so try the zombie servers
        if (tryDeadServers) {
            if (returnedRsp == null || returnedRsp.stateDescription == TaskState.ServerException) {
                // try the servers we previously skipped
                for (ServerWrapper wrapper : skipped) {
                    Callable<RequestTaskState> task = createRequestTask(wrapper.solrServer, req, true);
                    executer.submit(task);
                    inFlight++;
                    returnedRsp = getResponseIfReady(executer, inFlight >= maximumConcurrentRequests);
                    if (returnedRsp == null) {
                        log.info("Server :{} did not respond before the backUpRequestDelay time of {} elapsed",
                                wrapper.getKey(), backUpRequestDelay);
                        continue;
                    }
                    inFlight--;
                    if (returnedRsp.stateDescription == TaskState.ResponseReceived) {
                        return returnedRsp.response;
                    } else if (returnedRsp.stateDescription == TaskState.ServerException) {
                        ex = returnedRsp.exception;
                    } else if (returnedRsp.stateDescription == TaskState.RequestException) {
                        throw new SolrServerException(returnedRsp.exception);
                    }
                }
            }
        }

        // All current attempts could be slower than backUpRequestPause or returned
        // response could be from struggling server
        // so we need to wait until we get a good response or tasks all are
        // exhausted.
        if (returnedRsp == null || returnedRsp.stateDescription == TaskState.ServerException) {
            while (inFlight > 0) {
                returnedRsp = getResponseIfReady(executer, true);
                inFlight--;
                if (returnedRsp.stateDescription == TaskState.ResponseReceived) {
                    return returnedRsp.response;
                } else if (returnedRsp.stateDescription == TaskState.ServerException) {
                    ex = returnedRsp.exception;
                } else if (returnedRsp.stateDescription == TaskState.RequestException) {
                    throw new SolrServerException(returnedRsp.exception);
                }
            }
        }

        if (ex == null) {
            throw new SolrServerException("No live SolrServers available to handle this request");
        } else {
            throw new SolrServerException(
                    "No live SolrServers available to handle this request:" + zombieServers.keySet(), ex);
        }
    }

    @Override
    protected Exception addZombie(HttpSolrServer server, Exception e) {
        CharArrayWriter cw = new CharArrayWriter();
        PrintWriter pw = new PrintWriter(cw);
        e.printStackTrace(pw);
        pw.flush();
        String stack = cw.toString();
        log.info("Server :{} did not respond correctly or timed out, the server is zombied. {}", server.baseUrl,
                e.toString() + stack);
        return super.addZombie(server, e);
    }

    private Callable<RequestTaskState> createRequestTask(final HttpSolrServer server, final Req req,
            final boolean zombieAttempt) {

        Callable<RequestTaskState> task = new Callable<RequestTaskState>() {
            @Override
            public RequestTaskState call() throws Exception {
                Rsp rsp = new Rsp();
                rsp.server = server.getBaseURL();
                RequestTaskState taskState = new RequestTaskState();

                taskState.response = rsp;
                // Will be present so no need to check contains as the key will have been add by getUrls and there is nothing removing entries.

                inFlightRequestMonitor.increment(rsp.server);

                try {
                    rsp.rsp = server.request(req.getRequest());

                    taskState.stateDescription = TaskState.ResponseReceived;
                    if (zombieAttempt) {
                        zombieServers.remove(server);
                    }
                } catch (SolrException e) {
                    // we retry on 404 or 403 or 503 - you can see this on solr shutdown
                    if (e.code() == 404 || e.code() == 403 || e.code() == 503 || e.code() == 500) {
                        if (!zombieAttempt) {
                            addZombie(server, e);
                        }
                        taskState.setException(e, TaskState.ServerException);
                    } else {
                        // Server is alive but the request was likely malformed or invalid
                        taskState.setException(e, TaskState.RequestException);
                    }
                } catch (SocketException e) {
                    if (!zombieAttempt) {
                        addZombie(server, e);
                    }
                    taskState.setException(e, TaskState.ServerException);
                } catch (SocketTimeoutException e) {
                    if (!zombieAttempt) {
                        addZombie(server, e);
                    }
                    taskState.setException(e, TaskState.ServerException);
                } catch (SolrServerException e) {
                    Throwable rootCause = e.getRootCause();
                    if (rootCause instanceof IOException) {
                        if (!zombieAttempt) {
                            addZombie(server, e);
                        }
                        taskState.setException(e, TaskState.ServerException);
                    } else {
                        taskState.setException(e, TaskState.RequestException);
                    }
                } catch (Exception e) {
                    taskState.setException(e, TaskState.RequestException);
                }
                inFlightRequestMonitor.decrement(rsp.server);
                return taskState;
            }
        };
        return task;
    }

    private RequestTaskState getResponseIfReady(ExecutorCompletionService<RequestTaskState> executer,
            boolean waitUntilTaskComplete) throws SolrException {

        Future<RequestTaskState> taskInProgress = null;
        try {
            if (waitUntilTaskComplete) {
                taskInProgress = executer.take();
            } else {
                taskInProgress = executer.poll(backUpRequestDelay, TimeUnit.MILLISECONDS);
            }
            // could be null if poll time exceeded in which case return null.
            if (taskInProgress != null && !taskInProgress.isCancelled()) {
                RequestTaskState resp = taskInProgress.get();
                return resp;
            }
        } catch (InterruptedException e) {
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
        } catch (ExecutionException e) {
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
        }
        return null;
    }
}