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

org.apache.ignite.internal.managers.deployment.GridDeploymentClassLoader Maven / Gradle / Ivy

Go to download

Java-based middleware for in-memory processing of big data in a distributed environment.

There is a newer version: 3.0.0-beta1
Show newest version
/*
 * 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.
 */

package org.apache.ignite.internal.managers.deployment;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeoutException;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.configuration.DeploymentMode;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.util.GridBoundedLinkedHashSet;
import org.apache.ignite.internal.util.GridByteArrayList;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteUuid;
import org.jetbrains.annotations.Nullable;

/**
 * Class loader that is able to resolve task subclasses and resources
 * by requesting remote node. Every class that could not be resolved
 * by system class loader will be downloaded from given remote node
 * by task deployment identifier. If identifier has been changed on
 * remote node this class will throw exception.
 */
@SuppressWarnings({"CustomClassloader"})
class GridDeploymentClassLoader extends ClassLoader implements GridDeploymentInfo {
    /** Class loader ID. */
    private final IgniteUuid id;

    /** {@code True} for single node deployment. */
    private final boolean singleNode;

    /** Manager registry. */
    @GridToStringExclude
    private final GridKernalContext ctx;

    /** */
    @GridToStringExclude
    private final IgniteLogger log;

    /** Registered nodes. */
    @GridToStringExclude
    private LinkedList nodeList;

    /** Node ID -> Loader ID. */
    @GridToStringInclude
    private Map nodeLdrMap;

    /** */
    @GridToStringExclude
    private final GridDeploymentCommunication comm;

    /** */
    private final String[] p2pExclude;

    /** P2P timeout. */
    private final long p2pTimeout;

    /** Cache of missed resources names. */
    @GridToStringExclude
    private final GridBoundedLinkedHashSet missedRsrcs;

    /** Map of class byte code if it's not available locally. */
    @GridToStringExclude
    private final ConcurrentMap byteMap;

    /** User version. */
    private final String usrVer;

    /** Deployment mode. */
    private final DeploymentMode depMode;

    /** {@code True} to omit any output to INFO or higher. */
    private boolean quiet;

    /** */
    private final Object mux = new Object();

    /**
     * Creates a new peer class loader.
     * 

* If there is a security manager, its * {@link SecurityManager#checkCreateClassLoader()} * method is invoked. This may result in a security exception. * * @param id Class loader ID. * @param usrVer User version. * @param depMode Deployment mode. * @param singleNode {@code True} for single node. * @param ctx Kernal context. * @param parent Parent class loader. * @param clsLdrId Remote class loader identifier. * @param nodeId ID of node that have initiated task. * @param comm Communication manager loader will work through. * @param p2pTimeout Timeout for class-loading requests. * @param log Logger. * @param p2pExclude List of P2P loaded packages. * @param missedResourcesCacheSize Size of the missed resources cache. * @param clsBytesCacheEnabled Flag to enable class byte cache. * @param quiet {@code True} to omit output to log. * @throws SecurityException If a security manager exists and its * {@code checkCreateClassLoader} method doesn't allow creation * of a new class loader. */ GridDeploymentClassLoader( IgniteUuid id, String usrVer, DeploymentMode depMode, boolean singleNode, GridKernalContext ctx, ClassLoader parent, IgniteUuid clsLdrId, UUID nodeId, GridDeploymentCommunication comm, long p2pTimeout, IgniteLogger log, String[] p2pExclude, int missedResourcesCacheSize, boolean clsBytesCacheEnabled, boolean quiet) throws SecurityException { super(parent); assert id != null; assert depMode != null; assert ctx != null; assert comm != null; assert p2pTimeout > 0; assert log != null; assert clsLdrId != null; assert nodeId.equals(clsLdrId.globalId()); this.id = id; this.usrVer = usrVer; this.depMode = depMode; this.singleNode = singleNode; this.ctx = ctx; this.comm = comm; this.p2pTimeout = p2pTimeout; this.log = log; this.p2pExclude = p2pExclude; nodeList = new LinkedList<>(); nodeList.add(nodeId); Map map = U.newHashMap(1); map.put(nodeId, clsLdrId); nodeLdrMap = singleNode ? Collections.unmodifiableMap(map) : map; missedRsrcs = missedResourcesCacheSize > 0 ? new GridBoundedLinkedHashSet(missedResourcesCacheSize) : null; byteMap = clsBytesCacheEnabled ? new ConcurrentHashMap() : null; this.quiet = quiet; } /** * Creates a new peer class loader. *

* If there is a security manager, its * {@link SecurityManager#checkCreateClassLoader()} * method is invoked. This may result in a security exception. * * @param id Class loader ID. * @param usrVer User version. * @param depMode Deployment mode. * @param singleNode {@code True} for single node. * @param ctx Kernal context. * @param parent Parent class loader. * @param participants Participating nodes class loader map. * @param comm Communication manager loader will work through. * @param p2pTimeout Timeout for class-loading requests. * @param log Logger. * @param p2pExclude List of P2P loaded packages. * @param missedResourcesCacheSize Size of the missed resources cache. * @param clsBytesCacheEnabled Flag to enable class byte cache. * @param quiet {@code True} to omit output to log. * @throws SecurityException If a security manager exists and its * {@code checkCreateClassLoader} method doesn't allow creation * of a new class loader. */ GridDeploymentClassLoader( IgniteUuid id, String usrVer, DeploymentMode depMode, boolean singleNode, GridKernalContext ctx, ClassLoader parent, Map participants, GridDeploymentCommunication comm, long p2pTimeout, IgniteLogger log, String[] p2pExclude, int missedResourcesCacheSize, boolean clsBytesCacheEnabled, boolean quiet) throws SecurityException { super(parent); assert id != null; assert depMode != null; assert ctx != null; assert comm != null; assert p2pTimeout > 0; assert log != null; assert participants != null; this.id = id; this.usrVer = usrVer; this.depMode = depMode; this.singleNode = singleNode; this.ctx = ctx; this.comm = comm; this.p2pTimeout = p2pTimeout; this.log = log; this.p2pExclude = p2pExclude; nodeList = new LinkedList<>(participants.keySet()); nodeLdrMap = new HashMap<>(participants); missedRsrcs = missedResourcesCacheSize > 0 ? new GridBoundedLinkedHashSet(missedResourcesCacheSize) : null; byteMap = clsBytesCacheEnabled ? new ConcurrentHashMap() : null; this.quiet = quiet; } /** {@inheritDoc} */ @Override public IgniteUuid classLoaderId() { return id; } /** {@inheritDoc} */ @Override public DeploymentMode deployMode() { return depMode; } /** {@inheritDoc} */ @Override public String userVersion() { return usrVer; } /** {@inheritDoc} */ @Override public boolean localDeploymentOwner() { return false; } /** {@inheritDoc} */ @Override public long sequenceNumber() { return -1; } /** {@inheritDoc} */ @Override public Map participants() { synchronized (mux) { return new HashMap<>(nodeLdrMap); } } /** * Adds new node and remote class loader id to this class loader. * Class loader will ask all associated nodes for the class/resource * until find it. * * @param nodeId Participating node ID. * @param ldrId Participating class loader id. */ void register(UUID nodeId, IgniteUuid ldrId) { assert nodeId != null; assert ldrId != null; assert nodeId.equals(ldrId.globalId()); assert !singleNode; synchronized (mux) { if (missedRsrcs != null) missedRsrcs.clear(); /* * We need to put passed in node into the * first position in the list. */ // 1. Remove passed in node if any. nodeList.remove(nodeId); // 2. Add passed in node to the first position. nodeList.addFirst(nodeId); // 3. Put to map. nodeLdrMap.put(nodeId, ldrId); } } /** * Remove remote node and remote class loader id associated with it from * internal map. * * @param nodeId Participating node ID. * @return Removed class loader ID. */ @Nullable IgniteUuid unregister(UUID nodeId) { assert nodeId != null; synchronized (mux) { nodeList.remove(nodeId); return nodeLdrMap.remove(nodeId); } } /** * @return Registered nodes. */ Collection registeredNodeIds() { synchronized (mux) { return new ArrayList<>(nodeList); } } /** * @return Registered class loader IDs. */ Collection registeredClassLoaderIds() { Collection ldrIds = new LinkedList<>(); synchronized (mux) { for (IgniteUuid ldrId : nodeLdrMap.values()) ldrIds.add(ldrId); } return ldrIds; } /** * @param nodeId Node ID. * @return Class loader ID for node ID. */ IgniteUuid registeredClassLoaderId(UUID nodeId) { synchronized (mux) { return nodeLdrMap.get(nodeId); } } /** * Checks if node is participating in deployment. * * @param nodeId Node ID to check. * @param ldrId Class loader ID. * @return {@code True} if node is participating in deployment. */ boolean hasRegisteredNode(UUID nodeId, IgniteUuid ldrId) { assert nodeId != null; assert ldrId != null; IgniteUuid ldrId0; synchronized (mux) { ldrId0 = nodeLdrMap.get(nodeId); } return ldrId0 != null && ldrId0.equals(ldrId); } /** * @return {@code True} if class loader has registered nodes. */ boolean hasRegisteredNodes() { synchronized (mux) { return !nodeList.isEmpty(); } } /** * @param name Name of the class. * @return {@code True} if locally excluded. */ private boolean isLocallyExcluded(String name) { if (p2pExclude != null) { for (String path : p2pExclude) { // Remove star (*) at the end. if (path.endsWith("*")) path = path.substring(0, path.length() - 1); if (name.startsWith(path)) return true; } } return false; } /** {@inheritDoc} */ @Override public Class loadClass(String name) throws ClassNotFoundException { assert !Thread.holdsLock(mux); // Check if we have package name on list of P2P loaded. // ComputeJob must be always loaded locally to avoid // any possible class casting issues. Class cls = null; try { if (!"org.apache.ignite.compute.ComputeJob".equals(name)) { if (isLocallyExcluded(name)) // P2P loaded class. cls = p2pLoadClass(name, true); } if (cls == null) cls = loadClass(name, true); } catch (ClassNotFoundException e) { throw e; } // Catch Throwable to secure against any errors resulted from // corrupted class definitions or other user errors. catch (Exception e) { if (X.hasCause(e, TimeoutException.class)) throw e; throw new ClassNotFoundException("Failed to load class due to unexpected error: " + name, e); } return cls; } /** * Loads the class with the specified binary name. The * default implementation of this method searches for classes in the * following order: *

*

    *
  1. Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded.
  2. *
  3. Invoke the {@link #findClass(String)} method to find the class.
  4. *
*

If the class was found using the above steps, and the * {@code resolve} flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting {@code Class} object. * * @param name The binary name of the class. * @param resolve If {@code true} then resolve the class. * @return The resulting {@code Class} object. * @throws ClassNotFoundException If the class could not be found */ @Nullable private Class p2pLoadClass(String name, boolean resolve) throws ClassNotFoundException { assert !Thread.holdsLock(mux); // First, check if the class has already been loaded. Class cls = findLoadedClass(name); if (cls == null) cls = findClass(name); if (resolve) resolveClass(cls); return cls; } /** {@inheritDoc} */ @Nullable @Override protected Class findClass(String name) throws ClassNotFoundException { assert !Thread.holdsLock(mux); if (!isLocallyExcluded(name)) { // This is done for URI deployment in which case the parent loader // does not have the requested resource, but it is still locally // available. GridDeployment dep = ctx.deploy().getLocalDeployment(name); if (dep != null) { if (log.isDebugEnabled()) log.debug("Found class in local deployment [cls=" + name + ", dep=" + dep + ']'); return dep.deployedClass(name); } } String path = U.classNameToResourceName(name); GridByteArrayList byteSrc = sendClassRequest(name, path); synchronized (this) { Class cls = findLoadedClass(name); if (cls == null) { if (byteMap != null) byteMap.put(path, byteSrc.array()); cls = defineClass(name, byteSrc.internalArray(), 0, byteSrc.size()); /* Define package in classloader. See URLClassLoader.defineClass(). */ int i = name.lastIndexOf('.'); if (i != -1) { String pkgName = name.substring(0, i); if (getPackage(pkgName) == null) // Too much nulls is normal because we don't have package's meta info. definePackage(pkgName, null, null, null, null, null, null, null); } } if (log.isDebugEnabled()) log.debug("Loaded class [cls=" + name + ", ldr=" + this + ']'); return cls; } } /** * Computes end time based on timeout value passed in. * * @param timeout Timeout. * @return End time. */ private long computeEndTime(long timeout) { long endTime = U.currentTimeMillis() + timeout; // Account for overflow. if (endTime < 0) endTime = Long.MAX_VALUE; return endTime; } /** * Sends class-loading request to all nodes associated with this class loader. * * @param name Class name. * @param path Class path. * @return Class byte source. * @throws ClassNotFoundException If class was not found. */ private GridByteArrayList sendClassRequest(String name, String path) throws ClassNotFoundException { assert !Thread.holdsLock(mux); long endTime = computeEndTime(p2pTimeout); Collection nodeListCp; Map nodeLdrMapCp; synchronized (mux) { // Skip requests for the previously missed classes. if (missedRsrcs != null && missedRsrcs.contains(path)) throw new ClassNotFoundException("Failed to peer load class [class=" + name + ", nodeClsLdrIds=" + nodeLdrMap + ", parentClsLoader=" + getParent() + ']'); // If single-node mode, then node cannot change and we simply reuse list and map. // Otherwise, make copies that can be used outside synchronization. nodeListCp = singleNode ? nodeList : new LinkedList<>(nodeList); nodeLdrMapCp = singleNode ? nodeLdrMap : new HashMap<>(nodeLdrMap); } IgniteCheckedException err = null; TimeoutException te = null; for (UUID nodeId : nodeListCp) { if (nodeId.equals(ctx.discovery().localNode().id())) // Skip local node as it is already used as parent class loader. continue; IgniteUuid ldrId = nodeLdrMapCp.get(nodeId); ClusterNode node = ctx.discovery().node(nodeId); if (node == null) { if (log.isDebugEnabled()) log.debug("Found inactive node in class loader (will skip): " + nodeId); continue; } try { GridDeploymentResponse res = null; try { res = comm.sendResourceRequest(path, ldrId, node, endTime); } catch (TimeoutException e) { te = e; } if (res == null) { String msg = "Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ']'; if (!quiet) U.warn(log, msg); else if (log.isDebugEnabled()) log.debug(msg); err = new IgniteCheckedException(msg); continue; } if (res.success()) return res.byteSource(); // In case of shared resources/classes all nodes should have it. if (log.isDebugEnabled()) log.debug("Failed to find class on remote node [class=" + name + ", nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", reason=" + res.errorMessage() + ']'); synchronized (mux) { if (missedRsrcs != null) missedRsrcs.add(path); } throw new ClassNotFoundException("Failed to peer load class [class=" + name + ", nodeClsLdrs=" + nodeLdrMapCp + ", parentClsLoader=" + getParent() + ", reason=" + res.errorMessage() + ']'); } catch (IgniteCheckedException e) { // This thread should be interrupted again in communication if it // got interrupted. So we assume that thread can be interrupted // by processing cancellation request. if (Thread.currentThread().isInterrupted()) { if (!quiet) U.error(log, "Failed to find class probably due to task/job cancellation: " + name, e); else if (log.isDebugEnabled()) log.debug("Failed to find class probably due to task/job cancellation [name=" + name + ", err=" + e + ']'); } else { if (!quiet) U.warn(log, "Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); else if (log.isDebugEnabled()) log.debug("Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); err = e; } } } if (te != null) { err.addSuppressed(te); throw new IgniteException(err); } throw new ClassNotFoundException("Failed to peer load class [class=" + name + ", nodeClsLdrs=" + nodeLdrMapCp + ", parentClsLoader=" + getParent() + ']', err); } /** {@inheritDoc} */ @Nullable @Override public InputStream getResourceAsStream(String name) { try { return getResourceAsStreamEx(name); } catch (TimeoutException ignore) { return null; } } /** */ @Nullable public InputStream getResourceAsStreamEx(String name) throws TimeoutException { assert !Thread.holdsLock(mux); if (byteMap != null && name.endsWith(".class")) { byte[] bytes = byteMap.get(name); if (bytes != null) { if (log.isDebugEnabled()) log.debug("Got class definition from byte code cache: " + name); return new ByteArrayInputStream(bytes); } } InputStream in = ClassLoader.getSystemResourceAsStream(name); if (in == null) in = super.getResourceAsStream(name); // Most probably, this is initiated by GridUtils.getUserVersion(). // No point to send request. if ("META-INF/services/org.apache.commons.logging.LogFactory".equalsIgnoreCase(name)) { if (log.isDebugEnabled()) log.debug("Denied sending remote request for META-INF/services/org.apache.commons.logging.LogFactory."); return null; } if (in == null) in = sendResourceRequest(name); return in; } /** * Sends resource request to all remote nodes associated with this class loader. * * @param name Resource name. * @return InputStream for resource or {@code null} if resource could not be found. */ @Nullable private InputStream sendResourceRequest(String name) throws TimeoutException { assert !Thread.holdsLock(mux); long endTime = computeEndTime(p2pTimeout); Collection nodeListCp; Map nodeLdrMapCp; synchronized (mux) { // Skip requests for the previously missed classes. if (missedRsrcs != null && missedRsrcs.contains(name)) return null; // If single-node mode, then node cannot change and we simply reuse list and map. // Otherwise, make copies that can be used outside synchronization. nodeListCp = singleNode ? nodeList : new LinkedList<>(nodeList); nodeLdrMapCp = singleNode ? nodeLdrMap : new HashMap<>(nodeLdrMap); } for (UUID nodeId : nodeListCp) { if (nodeId.equals(ctx.discovery().localNode().id())) // Skip local node as it is already used as parent class loader. continue; IgniteUuid ldrId = nodeLdrMapCp.get(nodeId); ClusterNode node = ctx.discovery().node(nodeId); if (node == null) { if (log.isDebugEnabled()) log.debug("Found inactive node in class loader (will skip): " + nodeId); continue; } try { // Request is sent with timeout that is why we can use synchronization here. GridDeploymentResponse res = comm.sendResourceRequest(name, ldrId, node, endTime); if (res == null) { U.warn(log, "Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ']'); } else if (!res.success()) { synchronized (mux) { // Cache unsuccessfully loaded resource. if (missedRsrcs != null) missedRsrcs.add(name); } // Some frameworks like Spring often ask for the resources // just in case - none will happen if there are no such // resources. So we print out INFO level message. if (!quiet) { if (log.isInfoEnabled()) log.info("Failed to get resource from node [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", msg=" + res.errorMessage() + ']'); } else if (log.isDebugEnabled()) log.debug("Failed to get resource from node [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", msg=" + res.errorMessage() + ']'); // Do not ask other nodes in case of shared mode all of them should have the resource. return null; } else { return new ByteArrayInputStream(res.byteSource().internalArray(), 0, res.byteSource().size()); } } catch (IgniteCheckedException e) { // This thread should be interrupted again in communication if it // got interrupted. So we assume that thread can be interrupted // by processing cancellation request. if (Thread.currentThread().isInterrupted()) { if (!quiet) U.error(log, "Failed to get resource probably due to task/job cancellation: " + name, e); else if (log.isDebugEnabled()) log.debug("Failed to get resource probably due to task/job cancellation: " + name); } else { if (!quiet) U.warn(log, "Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); else if (log.isDebugEnabled()) log.debug("Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); } } } return null; } /** {@inheritDoc} */ @Override public String toString() { synchronized (mux) { return S.toString(GridDeploymentClassLoader.class, this); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy