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

io.undertow.server.handlers.proxy.mod_cluster.Node Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.
 */

package io.undertow.server.handlers.proxy.mod_cluster;

import static org.xnio.Bits.allAreClear;
import static org.xnio.Bits.anyAreSet;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

import io.undertow.UndertowLogger;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.proxy.ConnectionPoolManager;
import io.undertow.server.handlers.proxy.ProxyConnectionPool;
import org.xnio.OptionMap;
import io.undertow.connector.ByteBufferPool;
import org.xnio.XnioIoThread;

/**
 * @author Stuart Douglas
 * @author Emanuel Muckenhuber
 */
class Node {

    private final int id;
    private final String jvmRoute;
    private final ConnectionPoolManager connectionPoolManager;
    private final NodeConfig nodeConfig;
    private final Balancer balancerConfig;
    private final ProxyConnectionPool connectionPool;
    private final NodeLbStatus lbStatus = new NodeLbStatus();
    private final ModClusterContainer container;
    private final List vHosts = new CopyOnWriteArrayList<>();
    private final List contexts = new CopyOnWriteArrayList<>();

    private final XnioIoThread ioThread;
    private final ByteBufferPool bufferPool;

    private volatile int state = ERROR; // This gets cleared with the first status report

    private static final int ERROR = 1 << 31;
    private static final int REMOVED = 1 << 30;
    private static final int HOT_STANDBY = 1 << 29;
    private static final int ACTIVE_PING = 1 << 28;
    private static final int ERROR_MASK = (1 << 10) - 1;

    private static final AtomicInteger idGen = new AtomicInteger();
    private static final AtomicIntegerFieldUpdater stateUpdater = AtomicIntegerFieldUpdater.newUpdater(Node.class, "state");

    protected Node(NodeConfig nodeConfig, Balancer balancerConfig, XnioIoThread ioThread, ByteBufferPool bufferPool, ModClusterContainer container) {
        this.id = idGen.incrementAndGet();
        this.jvmRoute = nodeConfig.getJvmRoute();
        this.nodeConfig = nodeConfig;
        this.ioThread = ioThread;
        this.bufferPool = bufferPool;
        this.balancerConfig = balancerConfig;
        this.container = container;
        this.connectionPoolManager = new NodeConnectionPoolManager();
        this.connectionPool = new ProxyConnectionPool(connectionPoolManager, nodeConfig.getConnectionURI(), container.getXnioSsl(), container.getClient(), container.getClientOptions());
    }

    public int getId() {
        return id;
    }

    /**
     * Get the JVM route.
     *
     * @return the jvmRoute
     */
    public String getJvmRoute() {
        return jvmRoute;
    }

    public Balancer getBalancer() {
        return balancerConfig;
    }

    public NodeConfig getNodeConfig() {
        return nodeConfig;
    }

    /**
     * Get or create the connection pool for this node.
     *
     * @return the connection pool
     */
    public ProxyConnectionPool getConnectionPool() {
        return connectionPool;
    }

    XnioIoThread getIoThread() {
        return ioThread;
    }

    public NodeStatus getStatus() {
        final int status = this.state;
        if (anyAreSet(status, ERROR)) {
            return NodeStatus.NODE_DOWN;
        } else if (anyAreSet(status, HOT_STANDBY)) {
            return NodeStatus.NODE_HOT_STANDBY;
        } else {
            return NodeStatus.NODE_UP;
        }
    }

    public int getElected() {
        return lbStatus.getElected();
    }

    int getElectedDiff() {
        return lbStatus.getElectedDiff();
    }

    /**
     * Get the load information. Add the error information for clients.
     *
     * @return the node load
     */
    public int getLoad() {
        final int status = this.state;
        if (anyAreSet(status, ERROR)) {
            return -1;
        } else if (anyAreSet(status, HOT_STANDBY)) {
            return 0;
        } else {
            return lbStatus.getLbFactor();
        }
    }

    /**
     * Get the current load status, based on the number of elections and the current load;
     *
     * @return the load status
     */
    public int getLoadStatus() {
        return lbStatus.getLbStatus();
    }

    /**
     * This node got elected to serve a request!
     */
    void elected() {
        lbStatus.elected();
    }

    List getVHosts() {
        return Collections.unmodifiableList(vHosts);
    }

    Collection getContexts() {
        return Collections.unmodifiableCollection(contexts);
    }

    void resetLbStatus() {
        if (allAreClear(state, ERROR)) {
            if (lbStatus.update()) {
                return;
            }
        }
    }

    /**
     * Check the health of the node and try to ping it if necessary.
     *
     * @param threshold     the threshold after which the node should be removed
     * @param healthChecker the node health checker
     */
    protected void checkHealth(long threshold, NodeHealthChecker healthChecker) {
        final int state = this.state;
        if (anyAreSet(state, REMOVED | ACTIVE_PING)) {
            return;
        }
        healthCheckPing(threshold, healthChecker);
    }

    void healthCheckPing(final long threshold, NodeHealthChecker healthChecker) {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            if ((oldState & ACTIVE_PING) != 0) {
                // There is already a ping active
                return;
            }
            newState = oldState | ACTIVE_PING;
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                break;
            }
        }

        NodePingUtil.internalPingNode(this, new NodePingUtil.PingCallback() {
            @Override
            public void completed() {
                clearActivePing();
            }

            @Override
            public void failed() {
                if (healthCheckFailed() == threshold) {
                    // Remove using the executor task pool
                    ioThread.getWorker().execute(new Runnable() {
                        @Override
                        public void run() {
                            container.removeNode(Node.this, true);
                            clearActivePing();
                        }
                    });
                } else {
                    clearActivePing();
                }
            }
        }, healthChecker, ioThread, bufferPool, container.getClient(), container.getXnioSsl(), OptionMap.EMPTY);
    }

    /**
     * Async ping from the user
     *
     * @param exchange    the http server exchange
     * @param callback    the ping callback
     */
    void ping(final HttpServerExchange exchange, final NodePingUtil.PingCallback callback) {
        NodePingUtil.pingNode(this, exchange, callback);
    }

    /**
     * Register a context.
     *
     * @param path the context path
     * @return the created context
     */
    Context registerContext(final String path, final List virtualHosts) {
        VHostMapping host = null;
        for (final VHostMapping vhost : vHosts) {
            if (virtualHosts.equals(vhost.getAliases())) {
                host = vhost;
                break;
            }
        }
        if (host == null) {
            host = new VHostMapping(this, virtualHosts);
            vHosts.add(host);
        }
        final Context context = new Context(path, host, this);
        contexts.add(context);
        return context;
    }

    /**
     * Get a context.
     *
     * @param path       the context path
     * @param aliases    the aliases
     * @return the context, {@code null} if there is no matching context
     */
    Context getContext(final String path, List aliases) {
        VHostMapping host = null;
        for (final VHostMapping vhost : vHosts) {
            if (aliases.equals(vhost.getAliases())) {
                host = vhost;
                break;
            }
        }
        if (host == null) {
            return null;
        }
        for (final Context context : contexts) {
            if (context.getPath().equals(path) && context.getVhost() == host) {
                return context;
            }
        }
        return null;
    }

    Context removeContext(final String path, final List aliases) {
        final Context context = getContext(path, aliases);
        if (context != null) {
            context.stop();
            contexts.remove(context);
            return context;
        }
        return null;
    }

    protected void updateLoad(final int i) {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            newState = oldState & ~(ERROR | HOT_STANDBY | ERROR_MASK);
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                lbStatus.updateLoad(i);
                return;
            }
        }
    }

    protected void hotStandby() {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            newState = oldState & ~(ERROR | ERROR_MASK) | HOT_STANDBY;
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                lbStatus.updateLoad(0);
                return;
            }
        }
    }

    protected void markRemoved() {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            newState = oldState | REMOVED;
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                connectionPool.close();
                return;
            }
        }
    }

    protected void markInError() {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            newState = oldState | ERROR;
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                UndertowLogger.ROOT_LOGGER.nodeIsInError(jvmRoute);
                return;
            }
        }
    }

    private void clearActivePing() {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            newState = oldState & ~ACTIVE_PING;
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                return;
            }
        }
    }

    /**
     * Mark a node in error. Mod_cluster has a threshold after which broken nodes get removed.
     *
     * @return
     */
    private int healthCheckFailed() {
        int oldState, newState;
        for (;;) {
            oldState = this.state;
            if ((oldState & ERROR) != ERROR) {
                newState = oldState | ERROR;
                UndertowLogger.ROOT_LOGGER.nodeIsInError(jvmRoute);
            } else if ((oldState & ERROR_MASK) == ERROR_MASK) {
                return ERROR_MASK;
            } else {
                newState = oldState +1;
            }
            if (stateUpdater.compareAndSet(this, oldState, newState)) {
                return newState & ERROR_MASK;
            }
        }
    }

    protected void resetState() {
        state = ERROR;
        lbStatus.updateLoad(0);
    }

    protected boolean isInErrorState() {
        return (state & ERROR) == ERROR;
    }

    boolean isHotStandby() {
        return anyAreSet(state, HOT_STANDBY);
    }

    protected boolean checkAvailable(final boolean existingSession) {
        if (allAreClear(state, ERROR | REMOVED)) {
            // Check the state of the queue on the connection pool
            final ProxyConnectionPool.AvailabilityType availability = connectionPool.available();
            if (availability == ProxyConnectionPool.AvailabilityType.AVAILABLE) {
                return true;
            } else if (availability == ProxyConnectionPool.AvailabilityType.FULL) {
                if (existingSession) {
                    return true;
                } else if (nodeConfig.isQueueNewRequests()) {
                    return true;
                }
            }
        }
        return false;
    }

    private class NodeConnectionPoolManager implements ConnectionPoolManager {

        @Override
        public boolean isAvailable() {
            return allAreClear(state, ERROR | REMOVED);
        }

        @Override
        public boolean handleError() {
            markInError();
            return false;
        }

        @Override
        public boolean clearError() {
            // This needs to be cleared through the status update
            return isAvailable();
        }

        @Override
        public int getMaxConnections() {
            return nodeConfig.getMaxConnections();
        }

        @Override
        public int getMaxCachedConnections() {
            return nodeConfig.getMaxConnections();
        }

        @Override
        public int getSMaxConnections() {
            return nodeConfig.getSmax();
        }

        @Override
        public long getTtl() {
            return nodeConfig.getTtl();
        }

        @Override
        public int getMaxQueueSize() {
            return nodeConfig.getRequestQueueSize();
        }

        @Override
        public int getProblemServerRetry() {
            return -1; // Disable ping from the pool, this is handled through the health-check
        }
    }

    // Simple host mapping for the mod cluster management protocol
    static final AtomicInteger vHostIdGen = new AtomicInteger();
    static class VHostMapping {

        private final int id;
        private final List aliases;
        private final Node node;

        VHostMapping(Node node, List aliases) {
            this.id = vHostIdGen.incrementAndGet();
            this.aliases = aliases;
            this.node = node;
        }

        public int getId() {
            return id;
        }

        public List getAliases() {
            return aliases;
        }

        Node getNode() {
            return node;
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "jvmRoute='" + jvmRoute + '\'' +
                ", contexts=" + contexts +
                '}';
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy