/** * @param nodeId Node ID. * @return Class loader ID for node ID. */ GridTuple2<GridUuid, Long> getClassLoaderId(UUID nodeId) { assert nodeId != null; assert Thread.holdsLock(mux); return loader().registeredClassLoaderId(nodeId); }
private void addLineDef(@Nonnull final PlotLineDef toBeAdded) { if (!Thread.holdsLock(this)) throw new AssertionError(); List<PlotLineDef> registeredLineDefs = registeredLineDefsMap.get(toBeAdded); if (registeredLineDefs == null) { registeredLineDefs = new ArrayList<PlotLineDef>(); registeredLineDefsMap.put(toBeAdded, registeredLineDefs); } try { Iterables.find( registeredLineDefs, new Predicate<PlotLineDef>() { @Override public boolean apply(@Nullable PlotLineDef lineDef) { return lineDef == toBeAdded; } }); // already added } catch (NoSuchElementException e) { registeredLineDefs.add(toBeAdded); } }
/** {@inheritDoc} */ protected void getNewMonitors(Map<String, Monitor> map) throws MonitorException { assert Thread.holdsLock(this); int used = prologue.getUsed(); long modificationTime = prologue.getModificationTimeStamp(); if ((used > lastUsed) || (lastModificationTime > modificationTime)) { lastUsed = used; lastModificationTime = modificationTime; Monitor monitor = getNextMonitorEntry(); while (monitor != null) { String name = monitor.getName(); // guard against duplicate entries if (!map.containsKey(name)) { map.put(name, monitor); /* * insertedMonitors is null when called from pollFor() * via buildMonitorMap(). Since we update insertedMonitors * at the end of buildMonitorMap(), it's ok to skip the * add here. */ if (insertedMonitors != null) { insertedMonitors.add(monitor); } } monitor = getNextMonitorEntry(); } } }
/** * 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 hasParticipant(UUID nodeId, GridUuid ldrId) { assert nodeId != null; assert ldrId != null; assert Thread.holdsLock(mux); return loader().hasRegisteredNode(nodeId, ldrId); }
/** * Adds new participant to deployment. * * @param dep Shared deployment. * @param meta Request metadata. * @return {@code True} if participant was added. */ private boolean addParticipant(SharedDeployment dep, GridDeploymentMetadata meta) { assert dep != null; assert meta != null; assert Thread.holdsLock(mux); if (!checkModeMatch(dep, meta)) return false; if (meta.participants() != null) { for (Map.Entry<UUID, GridTuple2<GridUuid, Long>> e : meta.participants().entrySet()) { dep.addParticipant(e.getKey(), e.getValue().get1(), e.getValue().get2()); if (log.isDebugEnabled()) log.debug( "Added new participant [nodeId=" + e.getKey() + ", clsLdrId=" + e.getValue().get1() + ", seqNum=" + e.getValue().get2() + ']'); } } if (dep.deployMode() == CONTINUOUS || meta.participants() == null) { if (!dep.addParticipant(meta.senderNodeId(), meta.classLoaderId(), meta.sequenceNumber())) { U.warn( log, "Failed to create shared mode deployment " + "(requested class loader was already undeployed, did sender node leave grid?) " + "[clsLdrId=" + meta.classLoaderId() + ", senderNodeId=" + meta.senderNodeId() + ']'); return false; } if (log.isDebugEnabled()) log.debug( "Added new participant [nodeId=" + meta.senderNodeId() + ", clsLdrId=" + meta.classLoaderId() + ", seqNum=" + meta.sequenceNumber() + ']'); } return true; }
/** * @param meta Request metadata. * @return {@code True} if class loader is obsolete. */ private boolean isDeadClassLoader(GridDeploymentMetadata meta) { assert Thread.holdsLock(mux); synchronized (mux) { if (deadClsLdrs.contains(meta.classLoaderId())) { if (log.isDebugEnabled()) log.debug("Ignoring request for obsolete class loader: " + meta); return true; } return false; } }
/** Sets property removed. */ void onRemoved() { assert Thread.holdsLock(mux); removed = true; Collection<GridUuid> deadIds = loader().registeredClassLoaderIds(); if (log.isDebugEnabled()) log.debug("Registering dead class loader IDs: " + deadIds); synchronized (mux) { deadClsLdrs.addAll(deadIds); } }
/** @param nodeId Node ID to remove. */ void removeParticipant(UUID nodeId) { assert nodeId != null; assert Thread.holdsLock(mux); GridUuid ldrId = loader().unregister(nodeId); if (log.isDebugEnabled()) log.debug("Registering dead class loader ID: " + ldrId); synchronized (mux) { deadClsLdrs.add(ldrId); } }
/** {@inheritDoc} */ protected MonitorStatus getMonitorStatus(Map<String, Monitor> map) throws MonitorException { assert Thread.holdsLock(this); assert insertedMonitors != null; // load any new monitors getNewMonitors(map); // current implementation doesn't support deletion or reuse of entries ArrayList removed = EMPTY_LIST; ArrayList inserted = insertedMonitors; insertedMonitors = new ArrayList<Monitor>(); return new MonitorStatus(inserted, removed); }
/** * @param nodeId Grid node ID. * @param ldrId Class loader ID. * @param seqNum Sequence number for the class loader. * @return Whether actually added or not. */ boolean addParticipant(UUID nodeId, GridUuid ldrId, long seqNum) { assert nodeId != null; assert ldrId != null; assert Thread.holdsLock(mux); synchronized (mux) { if (!deadClsLdrs.contains(ldrId)) { loader().register(nodeId, ldrId, seqNum); return true; } return false; } }
/** * @param alias Class alias. * @return Deployed class. */ @Nullable private GridDeployment getDeployment(String alias) { assert Thread.holdsLock(mux); LinkedList<GridDeployment> deps = cache.get(alias); if (deps != null) { assert !deps.isEmpty(); GridDeployment dep = deps.getFirst(); if (!dep.isUndeployed()) { return dep; } } return null; }
// package-private int poll(int events, long timeout) throws IOException { assert Thread.holdsLock(blockingLock()) && !isBlocking(); synchronized (readLock) { int n = 0; try { begin(); synchronized (stateLock) { if (!isOpen()) return 0; readerThread = NativeThread.current(); } n = Net.poll(fd, events, timeout); } finally { readerThread = 0; end(n > 0); } return n; } }
/** {@inheritDoc} */ protected void buildMonitorMap(Map<String, Monitor> map) throws MonitorException { assert Thread.holdsLock(this); // start at the beginning of the buffer buffer.rewind(); // create pseudo monitors buildPseudoMonitors(map); // position buffer to start of the data section buffer.position(prologue.getSize()); nextEntry = buffer.position(); perfDataItem = 0; int used = prologue.getUsed(); long modificationTime = prologue.getModificationTimeStamp(); Monitor m = getNextMonitorEntry(); while (m != null) { map.put(m.getName(), m); m = getNextMonitorEntry(); } /* * set the last modification data. These are set to the values * recorded before parsing the data structure. This allows the * the data structure to be modified while the Map is being built. * The Map may contain more entries than indicated based on the * time stamp, but this is handled by ignoring duplicate entries * when the Map is updated in getNewMonitors(). */ lastUsed = used; lastModificationTime = modificationTime; // synchronize with the target jvm synchWithTarget(map); // work around 1.4.2 counter inititization bugs kludge(map); insertedMonitors = new ArrayList<Monitor>(map.values()); }
private <T> T doIndexTask(IndexTask<T> task, T defaultValue) { assert Thread.holdsLock(this); if (!isBroken) { try { return task.doTask(); } catch (Exception e1) { MavenLog.LOG.warn(e1); cleanupBrokenData(); try { open(); } catch (MavenIndexException e2) { MavenLog.LOG.warn(e2); } } } markAsBroken(); return defaultValue; }
private void removeLineDef(@Nonnull final PlotLineDef toBeRemoved) { if (!Thread.holdsLock(this)) throw new AssertionError(); List<PlotLineDef> registeredLineDefs = registeredLineDefsMap.get(toBeRemoved); if (registeredLineDefs != null) { Iterables.removeIf( registeredLineDefs, new Predicate<PlotLineDef>() { @Override public boolean apply(@Nullable PlotLineDef lineDef) { return lineDef == toBeRemoved; } }); if (registeredLineDefs.isEmpty()) { registeredLineDefsMap.remove(toBeRemoved); } } else { registeredLineDefsMap.remove(toBeRemoved); } }
/** {@inheritDoc} */ @Override public void onDeployed(Class<?> cls) { assert !Thread.holdsLock(mux); boolean isTask = isTask(cls); String msg = (isTask ? "Task" : "Class") + " was deployed in SHARED or CONTINUOUS mode: " + cls; int type = isTask ? EVT_TASK_DEPLOYED : EVT_CLASS_DEPLOYED; if (ctx.event().isRecordable(type)) { GridDeploymentEvent evt = new GridDeploymentEvent(); evt.nodeId(ctx.localNodeId()); evt.message(msg); evt.type(type); evt.alias(cls.getName()); ctx.event().record(evt); } if (log.isInfoEnabled()) log.info(msg); }
/** * Called to record all undeployed classes.. * * @param leftNodeId Left node ID. */ void recordUndeployed(@Nullable UUID leftNodeId) { assert !Thread.holdsLock(mux); for (Map.Entry<String, Class<?>> depCls : deployedClassMap().entrySet()) { boolean isTask = isTask(depCls.getValue()); String msg = (isTask ? "Task" : "Class") + " was undeployed in SHARED or CONTINUOUS mode: " + depCls.getValue(); int type = isTask ? EVT_TASK_UNDEPLOYED : EVT_CLASS_UNDEPLOYED; if (ctx.event().isRecordable(type)) { GridDeploymentEvent evt = new GridDeploymentEvent(); evt.nodeId(ctx.localNodeId()); evt.message(msg); evt.type(type); evt.alias(depCls.getKey()); ctx.event().record(evt); } if (log.isInfoEnabled()) log.info(msg); } if (isObsolete()) { // Resource cleanup. ctx.resource().onUndeployed(this); ctx.cache().onUndeployed(leftNodeId, loader()); clearSerializationCaches(); } }
@Override public void startRunInjectors(@NotNull final Document hostDocument, final boolean synchronously) { if (myProject.isDisposed()) return; if (!synchronously && ApplicationManager.getApplication().isWriteAccessAllowed()) return; // use cached to avoid recreate PSI in alien project final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); final PsiFile hostPsiFile = documentManager.getCachedPsiFile(hostDocument); if (hostPsiFile == null) return; final ConcurrentList<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); if (injected.isEmpty()) return; if (myProgress.isCanceled()) { myProgress = new DaemonProgressIndicator(); } final Set<DocumentWindow> newDocuments = Collections.synchronizedSet(new THashSet<>()); final Processor<DocumentWindow> commitProcessor = documentWindow -> { if (myProject.isDisposed()) return false; ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) return false; if (documentManager.isUncommited(hostDocument) || !hostPsiFile.isValid()) return false; // will be committed later // it is here where the reparse happens and old file contents replaced InjectedLanguageUtil.enumerate( documentWindow, hostPsiFile, (injectedPsi, places) -> { DocumentWindow newDocument = (DocumentWindow) injectedPsi.getViewProvider().getDocument(); if (newDocument != null) { PsiDocumentManagerBase.checkConsistency(injectedPsi, newDocument); newDocuments.add(newDocument); } }); return true; }; final Runnable commitInjectionsRunnable = () -> { if (myProgress.isCanceled()) return; JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<>(injected), myProgress, true, commitProcessor); synchronized (ourInjectionPsiLock) { injected.clear(); injected.addAll(newDocuments); } }; if (synchronously) { if (Thread.holdsLock(PsiLock.LOCK)) { // hack for the case when docCommit was called from within PSI modification, e.g. in // formatter. // we can't spawn threads to do injections there, otherwise a deadlock is imminent ContainerUtil.process(new ArrayList<>(injected), commitProcessor); } else { commitInjectionsRunnable.run(); } } else { JobLauncher.getInstance() .submitToJobThread( () -> ApplicationManagerEx.getApplicationEx() .tryRunReadAction(commitInjectionsRunnable), null); } }
/** @return Set of participating nodes. */ Collection<UUID> getParticipantNodeIds() { assert Thread.holdsLock(mux); return loader().registeredNodeIds(); }
public void startRunInjectors(@NotNull final Document hostDocument, final boolean synchronously) { if (myProject.isDisposed()) return; if (!synchronously && ApplicationManager.getApplication().isWriteAccessAllowed()) return; // use cached to avoid recreate PSI in alien project final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); final PsiFile hostPsiFile = documentManager.getCachedPsiFile(hostDocument); if (hostPsiFile == null) return; final CopyOnWriteArrayList<DocumentWindow> injected = (CopyOnWriteArrayList<DocumentWindow>) InjectedLanguageUtil.getCachedInjectedDocuments(hostPsiFile); if (injected.isEmpty()) return; if (myProgress.isCanceled()) { myProgress = new DaemonProgressIndicator(); } final Processor<DocumentWindow> commitProcessor = new Processor<DocumentWindow>() { @Override public boolean process(DocumentWindow documentWindow) { if (myProject.isDisposed()) return false; ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); if (indicator != null && indicator.isCanceled()) return false; if (documentManager.isUncommited(hostDocument) || !hostPsiFile.isValid()) return false; // will be committed later Segment[] ranges = documentWindow.getHostRanges(); Segment rangeMarker = ranges.length > 0 ? ranges[0] : null; PsiElement element = rangeMarker == null ? null : hostPsiFile.findElementAt(rangeMarker.getStartOffset()); if (element == null) { synchronized (PsiLock.LOCK) { injected.remove(documentWindow); } return true; } final DocumentWindow[] stillInjectedDocument = {null}; // it is here where the reparse happens and old file contents replaced InjectedLanguageUtil.enumerate( element, hostPsiFile, true, new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit( @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { stillInjectedDocument[0] = (DocumentWindow) injectedPsi.getViewProvider().getDocument(); PsiDocumentManagerImpl.checkConsistency(injectedPsi, stillInjectedDocument[0]); } }); synchronized (PsiLock.LOCK) { if (stillInjectedDocument[0] == null) { injected.remove(documentWindow); } else if (stillInjectedDocument[0] != documentWindow) { injected.remove(documentWindow); injected.addIfAbsent(stillInjectedDocument[0]); } } return true; } }; final Runnable commitInjectionsRunnable = new Runnable() { @Override public void run() { if (myProgress.isCanceled()) return; JobLauncher.getInstance() .invokeConcurrentlyUnderProgress( new ArrayList<DocumentWindow>(injected), myProgress, !synchronously, commitProcessor); } }; if (synchronously) { if (Thread.holdsLock(PsiLock.LOCK)) { // hack for the case when docCommit was called from within PSI modification, e.g. in // formatter. // we can't spawn threads to do injections there, otherwise a deadlock is imminent ContainerUtil.process(new ArrayList<DocumentWindow>(injected), commitProcessor); } else { commitInjectionsRunnable.run(); } } else { JobLauncher.getInstance() .submitToJobThread( Job.DEFAULT_PRIORITY, new Runnable() { @Override public void run() { ApplicationManagerEx.getApplicationEx() .tryRunReadAction(commitInjectionsRunnable); } }); } }
/** @return Registered class loader IDs. */ Collection<GridUuid> getClassLoaderIds() { assert Thread.holdsLock(mux); return loader().registeredClassLoaderIds(); }
/** @return {@code True} if deployment has any node participants. */ boolean hasParticipants() { assert Thread.holdsLock(mux); return loader().hasRegisteredNodes(); }
/** * Gets property removed. * * @return Property removed. */ boolean isRemoved() { assert Thread.holdsLock(mux); return removed; }
/** * @param depMode Deployment mode. * @param ldr Class loader to deploy. * @param cls Class. * @param alias Class alias. * @return Deployment. */ @SuppressWarnings({"ConstantConditions"}) private GridDeployment deploy( GridDeploymentMode depMode, ClassLoader ldr, Class<?> cls, String alias) { assert Thread.holdsLock(mux); LinkedList<GridDeployment> cachedDeps = null; GridDeployment dep = null; // Find existing class loader info. for (LinkedList<GridDeployment> deps : cache.values()) { for (GridDeployment d : deps) { if (d.classLoader() == ldr) { // Cache class and alias. d.addDeployedClass(cls, alias); cachedDeps = deps; dep = d; break; } } if (cachedDeps != null) { break; } } if (cachedDeps != null) { assert dep != null; cache.put(alias, cachedDeps); if (!cls.getName().equals(alias)) { // Cache by class name as well. cache.put(cls.getName(), cachedDeps); } return dep; } GridUuid ldrId = GridUuid.randomUuid(); long seqNum = seq.incrementAndGet(); String userVer = getUserVersion(ldr); dep = new GridDeployment(depMode, ldr, ldrId, seqNum, userVer, cls.getName(), true); dep.addDeployedClass(cls, alias); LinkedList<GridDeployment> deps = F.addIfAbsent(cache, alias, F.<GridDeployment>newLinkedList()); if (!deps.isEmpty()) { for (GridDeployment d : deps) { if (!d.isUndeployed()) { U.error( log, "Found more than one active deployment for the same resource " + "[cls=" + cls + ", depMode=" + depMode + ", dep=" + d + ']'); return null; } } } // Add at the beginning of the list for future fast access. deps.addFirst(dep); if (!cls.getName().equals(alias)) { // Cache by class name as well. cache.put(cls.getName(), deps); } if (log.isDebugEnabled()) { log.debug("Created new deployment: " + dep); } return dep; }
/** * Creates and caches new deployment. * * @param meta Deployment metadata. * @param isCache Whether or not to cache. * @return New deployment. */ private SharedDeployment createNewDeployment(GridDeploymentMetadata meta, boolean isCache) { assert Thread.holdsLock(mux); assert meta.parentLoader() == null; GridUuid ldrId = GridUuid.randomUuid(); GridDeploymentClassLoader clsLdr; if (meta.deploymentMode() == CONTINUOUS || meta.participants() == null) { // Create peer class loader. // Note that we are passing empty list for local P2P exclude, as it really // does not make sense with shared deployment. clsLdr = new GridDeploymentClassLoader( ldrId, meta.userVersion(), meta.deploymentMode(), false, ctx, ctxLdr, meta.classLoaderId(), meta.senderNodeId(), meta.sequenceNumber(), comm, ctx.config().getNetworkTimeout(), log, ctx.config().getPeerClassLoadingClassPathExclude(), ctx.config().getPeerClassLoadingMissedResourcesCacheSize(), meta.deploymentMode() == CONTINUOUS /* enable class byte cache in CONTINUOUS mode */); if (meta.participants() != null) for (Map.Entry<UUID, GridTuple2<GridUuid, Long>> e : meta.participants().entrySet()) clsLdr.register(e.getKey(), e.getValue().get1(), e.getValue().get2()); if (log.isDebugEnabled()) log.debug( "Created class loader in CONTINUOUS mode or without participants " + "[ldr=" + clsLdr + ", meta=" + meta + ']'); } else { assert meta.deploymentMode() == SHARED; // Create peer class loader. // Note that we are passing empty list for local P2P exclude, as it really // does not make sense with shared deployment. clsLdr = new GridDeploymentClassLoader( ldrId, meta.userVersion(), meta.deploymentMode(), false, ctx, ctxLdr, meta.participants(), comm, ctx.config().getNetworkTimeout(), log, ctx.config().getPeerClassLoadingClassPathExclude(), ctx.config().getPeerClassLoadingMissedResourcesCacheSize(), false); if (log.isDebugEnabled()) log.debug( "Created classloader in SHARED mode with participants " + "[ldr=" + clsLdr + ", meta=" + meta + ']'); } // Give this deployment a unique class loader to emphasize that this // ID is unique to this shared deployment and is not ID of loader on // sender node. SharedDeployment dep = new SharedDeployment( meta.deploymentMode(), clsLdr, ldrId, -1, meta.userVersion(), meta.alias()); if (log.isDebugEnabled()) log.debug("Created new deployment: " + dep); if (isCache) { List<SharedDeployment> deps = F.addIfAbsent(cache, meta.userVersion(), new LinkedList<SharedDeployment>()); assert deps != null; deps.add(dep); if (log.isDebugEnabled()) log.debug("Added deployment to cache: " + cache); } return dep; }
/** * Removes obsolete deployments in case of redeploy. * * @param meta Request metadata. * @return List of shares deployment. */ private GridTuple2<Boolean, SharedDeployment> checkRedeploy(GridDeploymentMetadata meta) { assert Thread.holdsLock(mux); SharedDeployment newDep = null; for (List<SharedDeployment> deps : cache.values()) { for (SharedDeployment dep : deps) { if (!dep.isUndeployed() && !dep.isPendingUndeploy()) { long undeployTimeout = ctx.config().getNetworkTimeout(); SharedDeployment doomed = null; // Only check deployments with no participants. if (!dep.hasParticipants()) { // In case of SHARED deployment it is possible to get hear if // unmarshalling happens during undeploy. In this case, we // simply don't do anything. if (dep.deployMode() == CONTINUOUS) { if (dep.existingDeployedClass(meta.className()) != null) { // Change from shared deploy to shared undeploy or user version change. // Simply remove all deployments with no participating nodes. if (meta.deploymentMode() == SHARED || !meta.userVersion().equals(dep.userVersion())) doomed = dep; } } } // If there are participants, we undeploy if class loader ID on some node changed. else if (dep.existingDeployedClass(meta.className()) != null) { GridTuple2<GridUuid, Long> ldr = dep.getClassLoaderId(meta.senderNodeId()); if (ldr != null) { if (!ldr.get1().equals(meta.classLoaderId())) { // If deployed sequence number is less, then schedule for undeployment. if (ldr.get2() < meta.sequenceNumber()) { if (log.isDebugEnabled()) log.debug( "Received request for a class with newer sequence number " + "(will schedule current class for undeployment) [newSeq=" + meta.sequenceNumber() + ", oldSeq=" + ldr.get2() + ", senderNodeId=" + meta.senderNodeId() + ", newClsLdrId=" + meta.classLoaderId() + ", oldClsLdrId=" + ldr.get1() + ']'); doomed = dep; } else if (ldr.get2() > meta.sequenceNumber()) { long time = System.currentTimeMillis() - dep.timestamp(); if (newDep == null && time < ctx.config().getNetworkTimeout()) { // Set undeployTimeout, so the class will be scheduled // for undeployment. undeployTimeout = ctx.config().getNetworkTimeout() - time; if (log.isDebugEnabled()) log.debug( "Received execution request for a stale class (will deploy and " + "schedule undeployment in " + undeployTimeout + "ms) " + "[curSeq=" + ldr.get2() + ", staleSeq=" + meta.sequenceNumber() + ", cls=" + meta.className() + ", senderNodeId=" + meta.senderNodeId() + ", curLdrId=" + ldr.get1() + ", staleLdrId=" + meta.classLoaderId() + ']'); // We got the redeployed class before the old one. // Simply create a temporary deployment for the sender node, // and schedule undeploy for it. newDep = createNewDeployment(meta, false); doomed = newDep; } else { U.warn( log, "Received execution request for a class that has been redeployed " + "(will ignore): " + meta.alias()); if (log.isDebugEnabled()) log.debug( "Received execution request for a class that has been redeployed " + "(will ignore) [alias=" + meta.alias() + ", dep=" + dep + ']'); return F.t(false, null); } } else { U.error( log, "Sequence number does not correspond to class loader ID [seqNum=" + meta.sequenceNumber() + ", dep=" + dep + ']'); return F.t(false, null); } } } } if (doomed != null) { doomed.onUndeployScheduled(); if (log.isDebugEnabled()) log.debug("Deployment was scheduled for undeploy: " + doomed); // Lifespan time. final long endTime = System.currentTimeMillis() + undeployTimeout; // Deployment to undeploy. final SharedDeployment undep = doomed; ctx.timeout() .addTimeoutObject( new GridTimeoutObject() { @Override public GridUuid timeoutId() { return undep.classLoaderId(); } @Override public long endTime() { return endTime < 0 ? Long.MAX_VALUE : endTime; } @Override public void onTimeout() { boolean removed = false; // Hot redeployment. synchronized (mux) { assert undep.isPendingUndeploy(); if (!undep.isUndeployed()) { undep.undeploy(); undep.onRemoved(); removed = true; Collection<SharedDeployment> deps = cache.get(undep.userVersion()); if (deps != null) { for (Iterator<SharedDeployment> i = deps.iterator(); i.hasNext(); ) if (i.next() == undep) i.remove(); if (deps.isEmpty()) cache.remove(undep.userVersion()); } if (log.isInfoEnabled()) log.info( "Undeployed class loader due to deployment mode change, " + "user version change, or hot redeployment: " + undep); } } // Outside synchronization. if (removed) undep.recordUndeployed(null); } }); } } } } if (newDep != null) { List<SharedDeployment> list = F.addIfAbsent(cache, meta.userVersion(), F.<SharedDeployment>newList()); assert list != null; list.add(newDep); } return F.t(true, newDep); }