/* $Id$ */ /** * 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.manifoldcf.crawler.reprioritizationtracker; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.agents.interfaces.*; import org.apache.manifoldcf.crawler.interfaces.*; import org.apache.manifoldcf.crawler.system.Logging; import java.nio.charset.StandardCharsets; import java.util.*; import java.io.*; /** This class tracks cluster-wide reprioritization operations. * These operations are driven forward by whatever thread needs them, * and are completed if those processes die by the threads that clean up * after the original process. */ public class ReprioritizationTracker implements IReprioritizationTracker { public static final String _rcsid = "@(#)$Id$"; protected final static String trackerWriteLock = "_REPR_TRACKER_LOCK_"; protected final static String trackerProcessIDResource = "_REPR_TRACKER_PID_"; protected final static String trackerReproIDResource = "_REPR_TRACKER_RID_"; protected final static String trackerMinimumDepthResource = "_REPR_MINDEPTH_"; /** Lock manager */ protected final ILockManager lockManager; protected final IBinManager binManager; /** Preload requests */ protected final Map<PreloadKey,PreloadRequest> preloadRequests = new HashMap<PreloadKey,PreloadRequest>(); /** Preload values */ protected final Map<PreloadKey,PreloadedValues> preloadedValues = new HashMap<PreloadKey,PreloadedValues>(); /** Constructor. */ public ReprioritizationTracker(IThreadContext threadContext) throws ManifoldCFException { lockManager = LockManagerFactory.make(threadContext); binManager = BinManagerFactory.make(threadContext); } /** Start a reprioritization activity. *@param prioritizationTime is the timestamp of the prioritization. *@param processID is the process ID of the process performing/waiting for the prioritization * to complete. *@param reproID is the reprocessing thread ID */ @Override public void startReprioritization(String processID, String reproID) throws ManifoldCFException { lockManager.enterWriteLock(trackerWriteLock); try { String currentProcessID = readProcessID(); if (currentProcessID != null) { // Already a reprioritization in progress. return; } writeProcessID(processID); writeReproID(reproID); try { binManager.reset(); } catch (Throwable e) { writeProcessID(null); writeReproID(null); if (e instanceof Error) throw (Error)e; else if (e instanceof RuntimeException) throw (RuntimeException)e; else if (e instanceof ManifoldCFException) throw (ManifoldCFException)e; else throw new RuntimeException("Unknown exception: "+e.getClass().getName()+": "+e.getMessage(),e); } writeMinimumDepth(0.0); } finally { lockManager.leaveWriteLock(trackerWriteLock); } } /** Complete a reprioritization activity. Prioritization will be marked as complete * only if the processID matches the one that started the current reprioritization. *@param processID is the process ID of the process completing the prioritization. */ @Override public void doneReprioritization(String reproID) throws ManifoldCFException { lockManager.enterWriteLock(trackerWriteLock); try { String currentProcessID = readProcessID(); String currentReproID = readReproID(); if (currentProcessID != null && currentReproID != null && currentReproID.equals(reproID)) { // Null out the fields writeProcessID(null); writeReproID(null); } } finally { lockManager.leaveWriteLock(trackerWriteLock); } } /** Check if the specified processID is the one performing reprioritization. *@param processID is the process ID to check. *@return the repro ID if the processID is confirmed to be the one. */ @Override public String isSpecifiedProcessReprioritizing(String processID) throws ManifoldCFException { lockManager.enterWriteLock(trackerWriteLock); try { String currentProcessID = readProcessID(); String currentReproID = readReproID(); if (currentProcessID != null && currentReproID != null && currentProcessID.equals(processID)) return currentReproID; return null; } finally { lockManager.leaveWriteLock(trackerWriteLock); } } /** Assess the current minimum depth. * This method is called to provide information about the priorities of the documents being currently * queued. It is the case that it is unoptimal to assign document priorities that are fundamentally higher than this value, * because then the new documents will be preferentially queued, and the goal of distributing documents across bins will not be * adequately met. *@param binNamesSet is the current set of priorities we see on the queuing operation. */ @Override public void assessMinimumDepth(Double[] binNamesSet) throws ManifoldCFException { double newMinPriority = Double.MAX_VALUE; for (Double binValue : binNamesSet) { if (binValue.doubleValue() < newMinPriority) newMinPriority = binValue.doubleValue(); } if (newMinPriority != Double.MAX_VALUE) { lockManager.enterWriteLock(trackerWriteLock); try { String processID = readProcessID(); if (processID == null) { double currentMinimumDepth = readMinimumDepth(); // Convert minPriority to minDepth. // Note that this calculation does not take into account anything having to do with connection rates, throttling, // or other adjustment factors. It allows us only to obtain the "raw" minimum depth: the depth without any // adjustments. double newMinDepth = Math.exp(newMinPriority)-1.0; if (newMinDepth > currentMinimumDepth) { writeMinimumDepth(newMinDepth); if (Logging.scheduling.isDebugEnabled()) Logging.scheduling.debug("Setting new minimum depth value to "+new Double(currentMinimumDepth).toString()); } else { if (newMinDepth < currentMinimumDepth && Logging.scheduling.isDebugEnabled()) Logging.scheduling.debug("Minimum depth value seems to have been set too high too early! currentMin = "+new Double(currentMinimumDepth).toString()+"; queue value = "+new Double(newMinDepth).toString()); } } } finally { lockManager.leaveWriteLock(trackerWriteLock); } } } /** Retrieve current minimum depth. *@return the current minimum depth to use. */ @Override public double getMinimumDepth() throws ManifoldCFException { lockManager.enterReadLock(trackerWriteLock); try { return readMinimumDepth(); } finally { lockManager.leaveReadLock(trackerWriteLock); } } /** Note preload amounts. */ @Override public void addPreloadRequest(String connectorClass, String binName, double weightedMinimumDepth) { final PreloadKey pk = new PreloadKey(connectorClass, binName); PreloadRequest pr = preloadRequests.get(pk); if (pr == null) { pr = new PreloadRequest(weightedMinimumDepth); preloadRequests.put(pk,pr); } else pr.updateRequest(weightedMinimumDepth); } /** Preload bin values. Call this OUTSIDE of a transaction. */ @Override public void preloadBinValues() throws ManifoldCFException { for (PreloadKey pk : preloadRequests.keySet()) { PreloadRequest pr = preloadRequests.get(pk); double[] newValues = binManager.getIncrementBinValuesInTransaction(pk.connectorClass, pk.binName, pr.getWeightedMinimumDepth(), pr.getRequestCount()); PreloadedValues pv = new PreloadedValues(newValues); preloadedValues.put(pk,pv); } preloadRequests.clear(); } /** Clear any preload requests. */ @Override public void clearPreloadRequests() { preloadRequests.clear(); } /** Clear remaining preloaded values. */ @Override public void clearPreloadedValues() { preloadedValues.clear(); } /** Get a bin value. *@param connectorClass is the connector class name. *@param binName is the bin name. *@param weightedMinimumDepth is the minimum depth to use. *@return the bin value. */ @Override public double getIncrementBinValue(String connectorClass, String binName, double weightedMinimumDepth) throws ManifoldCFException { final PreloadKey key = new PreloadKey(connectorClass,binName); PreloadedValues pv = preloadedValues.get(key); if (pv != null) { Double rval = pv.getNextValue(); if (rval != null) return rval.doubleValue(); } return binManager.getIncrementBinValues(connectorClass, binName, weightedMinimumDepth,1)[0]; } // Protected methods /** Read process ID. *@return processID, or null if none. */ protected String readProcessID() throws ManifoldCFException { byte[] processIDData = lockManager.readData(trackerProcessIDResource); if (processIDData == null) return null; else return new String(processIDData, StandardCharsets.UTF_8); } /** Write process ID. *@param processID is the process ID to write. */ protected void writeProcessID(String processID) throws ManifoldCFException { if (processID == null) lockManager.writeData(trackerProcessIDResource, null); else { byte[] processIDData = processID.getBytes(StandardCharsets.UTF_8); lockManager.writeData(trackerProcessIDResource, processIDData); } } /** Read repriotization ID. *@return reproID, or null if none. */ protected String readReproID() throws ManifoldCFException { byte[] reproIDData = lockManager.readData(trackerReproIDResource); if (reproIDData == null) return null; else return new String(reproIDData, StandardCharsets.UTF_8); } /** Write repro ID. *@param reproID is the repro ID to write. */ protected void writeReproID(String reproID) throws ManifoldCFException { if (reproID == null) lockManager.writeData(trackerReproIDResource, null); else { byte[] reproIDData = reproID.getBytes(StandardCharsets.UTF_8); lockManager.writeData(trackerReproIDResource, reproIDData); } } /** Read minimum depth. *@return the minimum depth. */ protected double readMinimumDepth() throws ManifoldCFException { byte[] data = lockManager.readData(trackerMinimumDepthResource); if (data == null || data.length != 8) return 0.0; long dataLong = (((long)data[0]) & 0xffL) + ((((long)data[1]) << 8) & 0xff00L) + ((((long)data[2]) << 16) & 0xff0000L) + ((((long)data[3]) << 24) & 0xff000000L) + ((((long)data[4]) << 32) & 0xff00000000L) + ((((long)data[5]) << 40) & 0xff0000000000L) + ((((long)data[6]) << 48) & 0xff000000000000L) + ((((long)data[7]) << 56) & 0xff00000000000000L); return Double.longBitsToDouble(dataLong); } /** Write minimum depth. *@param the minimum depth. */ protected void writeMinimumDepth(double depth) throws ManifoldCFException { long dataLong = Double.doubleToLongBits(depth); byte[] data = new byte[8]; data[0] = (byte)(dataLong & 0xffL); data[1] = (byte)((dataLong >> 8) & 0xffL); data[2] = (byte)((dataLong >> 16) & 0xffL); data[3] = (byte)((dataLong >> 24) & 0xffL); data[4] = (byte)((dataLong >> 32) & 0xffL); data[5] = (byte)((dataLong >> 40) & 0xffL); data[6] = (byte)((dataLong >> 48) & 0xffL); data[7] = (byte)((dataLong >> 56) & 0xffL); lockManager.writeData(trackerMinimumDepthResource,data); } /** A preload request */ protected static class PreloadRequest { protected double weightedMinimumDepth; protected int requestCount; public PreloadRequest(double weightedMinimumDepth) { this.weightedMinimumDepth = weightedMinimumDepth; this.requestCount = 1; } public void updateRequest(double weightedMinimumDepth) { if (this.weightedMinimumDepth < weightedMinimumDepth) this.weightedMinimumDepth = weightedMinimumDepth; requestCount++; } public double getWeightedMinimumDepth() { return weightedMinimumDepth; } public int getRequestCount() { return requestCount; } } /** A set of preloaded values */ protected static class PreloadedValues { protected double[] values; protected int valueIndex; public PreloadedValues(double[] values) { this.values = values; this.valueIndex = valueIndex; } public Double getNextValue() { if (valueIndex == values.length) return null; return new Double(values[valueIndex++]); } } /** Connector class name, bin name pair */ protected static class PreloadKey { public final String connectorClass; public final String binName; public PreloadKey(final String connectorClass, final String binName) { this.connectorClass = connectorClass; this.binName = binName; } public int hashCode() { return connectorClass.hashCode() + binName.hashCode(); } public boolean equals(final Object o) { if (!(o instanceof PreloadKey)) return false; final PreloadKey pk = (PreloadKey)o; return connectorClass.equals(pk.connectorClass) && binName.equals(pk.binName); } } }