mirror of
https://github.com/internetarchive/heritrix3.git
synced 2026-09-24 22:55:45 +00:00
[HER-1704] WorkQueueFrontier.snoozedClassQueues not bounded in size, not reported in useful order
* WorkQueueFrontier.java
add snoozedOverflow map of (wakeTime)->(DelayedWorkQueue) for snoozes over a capped size
maintain in parallel to snoozedClassQueues DelayQueue, maintain atomic size count
in standard report, presort snoozedClassQueues before displaying
* DelayedWorkQueue.java
move to top-level so serialization doesn't pull out whole frontier
* BdbFrontier.java
initialize snoozedOverflow
* BdbMultipleWorkQueues.java
improve error message seen on problematic checkpoint-recovery
This commit is contained in:
@@ -290,11 +290,12 @@ implements Checkpointable, BeanNameAware {
|
||||
|
||||
retiredQueues = bdb.getStoredQueue("retiredQueues", String.class, false);
|
||||
|
||||
// small risk of OutOfMemoryError: in large crawls with many
|
||||
// unresponsive queues, an unbounded number of snoozed queues
|
||||
// may exist
|
||||
// primary snoozed queues
|
||||
snoozedClassQueues = new DelayQueue<DelayedWorkQueue>();
|
||||
|
||||
// just in case: overflow for extreme situations
|
||||
snoozedOverflow = bdb.getStoredMap(
|
||||
"snoozedOverflow", Long.class, DelayedWorkQueue.class, true, false);
|
||||
|
||||
this.futureUris = bdb.getStoredMap(
|
||||
"futureUris", Long.class, CrawlURI.class, true, recoveryCheckpoint!=null);
|
||||
|
||||
|
||||
@@ -337,7 +337,7 @@ public class BdbMultipleWorkQueues {
|
||||
}
|
||||
|
||||
if (status!=OperationStatus.SUCCESS) {
|
||||
LOGGER.severe("failed; "+status+ " "+curi);
|
||||
LOGGER.severe("URI enqueueing failed; "+status+ " "+curi);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* This file is part of the Heritrix web crawler (crawler.archive.org).
|
||||
*
|
||||
* Licensed to the Internet Archive (IA) by one or more individual
|
||||
* contributors.
|
||||
*
|
||||
* The IA 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.archive.crawler.frontier;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A named WorkQueue wrapped with a wake time, perhaps referenced only
|
||||
* by name.
|
||||
*
|
||||
* @contributor gojomo
|
||||
*/
|
||||
class DelayedWorkQueue implements Delayed, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String classKey;
|
||||
public long wakeTime;
|
||||
|
||||
/**
|
||||
* Reference to the WorkQueue, perhaps saving a deserialization
|
||||
* from allQueues.
|
||||
*/
|
||||
protected transient WorkQueue workQueue;
|
||||
|
||||
public DelayedWorkQueue(WorkQueue queue) {
|
||||
this.classKey = queue.getClassKey();
|
||||
this.wakeTime = queue.getWakeTime();
|
||||
this.workQueue = queue;
|
||||
}
|
||||
|
||||
// TODO: consider if this should be method on WorkQueueFrontier
|
||||
public WorkQueue getWorkQueue(WorkQueueFrontier wqf) {
|
||||
if (workQueue == null) {
|
||||
// This is a recently deserialized DelayedWorkQueue instance
|
||||
WorkQueue result = wqf.getQueueFor(classKey);
|
||||
this.workQueue = result;
|
||||
}
|
||||
return workQueue;
|
||||
}
|
||||
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return unit.convert(
|
||||
wakeTime - System.currentTimeMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public String getClassKey() {
|
||||
return classKey;
|
||||
}
|
||||
|
||||
public long getWakeTime() {
|
||||
return wakeTime;
|
||||
}
|
||||
|
||||
public void setWakeTime(long time) {
|
||||
this.wakeTime = time;
|
||||
}
|
||||
|
||||
public int compareTo(Delayed obj) {
|
||||
if (this == obj) {
|
||||
return 0; // for exact identity only
|
||||
}
|
||||
DelayedWorkQueue other = (DelayedWorkQueue) obj;
|
||||
if (wakeTime > other.getWakeTime()) {
|
||||
return 1;
|
||||
}
|
||||
if (wakeTime < other.getWakeTime()) {
|
||||
return -1;
|
||||
}
|
||||
// at this point, the ordering is arbitrary, but still
|
||||
// must be consistent/stable over time
|
||||
return this.classKey.compareTo(other.getClassKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,9 +30,8 @@ import static org.archive.modules.fetcher.FetchStatusCodes.S_RUNTIME_EXCEPTION;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -43,6 +42,7 @@ import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.Delayed;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -50,6 +50,7 @@ import java.util.regex.Pattern;
|
||||
import org.apache.commons.collections.Bag;
|
||||
import org.apache.commons.collections.BagUtils;
|
||||
import org.apache.commons.collections.bag.HashBag;
|
||||
import org.apache.commons.collections.iterators.ObjectArrayIterator;
|
||||
import org.archive.crawler.datamodel.UriUniqFilter;
|
||||
import org.archive.crawler.datamodel.UriUniqFilter.CrawlUriReceiver;
|
||||
import org.archive.crawler.event.CrawlURIDispositionEvent;
|
||||
@@ -61,8 +62,6 @@ import org.archive.spring.KeyedProperties;
|
||||
import org.archive.util.ArchiveUtils;
|
||||
import org.archive.util.ObjectIdentityCache;
|
||||
import org.archive.util.ObjectIdentityMemCache;
|
||||
import org.archive.util.Transform;
|
||||
import org.archive.util.Transformer;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -212,7 +211,11 @@ implements Closeable,
|
||||
* All per-class queues held in snoozed state, sorted by wake time.
|
||||
*/
|
||||
transient protected DelayQueue<DelayedWorkQueue> snoozedClassQueues;
|
||||
protected StoredSortedMap<Long,DelayedWorkQueue> snoozedOverflow;
|
||||
protected AtomicInteger snoozedOverflowCount = new AtomicInteger(0);
|
||||
protected static int MAX_SNOOZED_IN_MEMORY = 5000;
|
||||
|
||||
/** URIs scheduled for reenqueuing at future date*/
|
||||
protected StoredSortedMap<Long, CrawlURI> futureUris;
|
||||
|
||||
transient protected WorkQueue longestActiveQueue = null;
|
||||
@@ -884,10 +887,32 @@ implements Closeable,
|
||||
protected void wakeQueues() {
|
||||
DelayedWorkQueue waked;
|
||||
while((waked = snoozedClassQueues.poll())!=null) {
|
||||
WorkQueue queue = waked.getWorkQueue();
|
||||
WorkQueue queue = waked.getWorkQueue(this);
|
||||
queue.setWakeTime(0);
|
||||
reenqueueQueue(queue);
|
||||
}
|
||||
// also consider overflow (usually empty)
|
||||
long now = System.currentTimeMillis();
|
||||
Iterator<DelayedWorkQueue> iter =
|
||||
snoozedOverflow.values().iterator();
|
||||
while(iter.hasNext()) {
|
||||
DelayedWorkQueue dq = iter.next();
|
||||
if(dq.getWakeTime()<=now) {
|
||||
iter.remove();
|
||||
snoozedOverflowCount.decrementAndGet();
|
||||
WorkQueue queue = dq.getWorkQueue(this);
|
||||
queue.setWakeTime(0);
|
||||
reenqueueQueue(queue);
|
||||
continue; // while
|
||||
}
|
||||
if(snoozedClassQueues.size()<MAX_SNOOZED_IN_MEMORY) {
|
||||
iter.remove();
|
||||
snoozedOverflowCount.decrementAndGet();
|
||||
snoozedClassQueues.add(dq);
|
||||
} else {
|
||||
break; // while
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1029,12 +1054,13 @@ implements Closeable,
|
||||
private void snoozeQueue(WorkQueue wq, long now, long delay_ms) {
|
||||
long nextTime = now + delay_ms;
|
||||
wq.setWakeTime(nextTime);
|
||||
// long snoozeToInactiveDelayMs = get(SNOOZE_DEACTIVATE_MS);
|
||||
// if (delay_ms > snoozeToInactiveDelayMs && !inactiveQueues.isEmpty()) {
|
||||
// deactivateQueue(wq);
|
||||
// } else {
|
||||
snoozedClassQueues.add(new DelayedWorkQueue(wq));
|
||||
// }
|
||||
DelayedWorkQueue dq = new DelayedWorkQueue(wq);
|
||||
if(snoozedClassQueues.size()<MAX_SNOOZED_IN_MEMORY) {
|
||||
snoozedClassQueues.add(dq);
|
||||
} else {
|
||||
snoozedOverflow.put(nextTime, dq);
|
||||
snoozedOverflowCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1096,7 +1122,7 @@ implements Closeable,
|
||||
int allCount = allQueues.size();
|
||||
int inProcessCount = inProcessQueues.uniqueSet().size();
|
||||
int readyCount = readyClassQueues.size();
|
||||
int snoozedCount = snoozedClassQueues.size();
|
||||
int snoozedCount = getSnoozedCount();
|
||||
int activeCount = inProcessCount + readyCount + snoozedCount;
|
||||
int inactiveCount = getTotalEligibleInactiveQueues();
|
||||
int ineligibleCount = getTotalIneligibleInactiveQueues();
|
||||
@@ -1132,7 +1158,7 @@ implements Closeable,
|
||||
int allCount = allQueues.size();
|
||||
int inProcessCount = inProcessQueues.uniqueSet().size();
|
||||
int readyCount = readyClassQueues.size();
|
||||
int snoozedCount = snoozedClassQueues.size();
|
||||
int snoozedCount = getSnoozedCount();
|
||||
int activeCount = inProcessCount + readyCount + snoozedCount;
|
||||
int inactiveCount = getTotalEligibleInactiveQueues();
|
||||
int ineligibleCount = getTotalIneligibleInactiveQueues();
|
||||
@@ -1251,6 +1277,7 @@ implements Closeable,
|
||||
|
||||
writer.print("\n -----===== SNOOZED QUEUES =====-----\n");
|
||||
queueSingleLinesTo(writer, this.snoozedClassQueues.iterator());
|
||||
queueSingleLinesTo(writer, this.snoozedOverflow.values().iterator());
|
||||
|
||||
writer.print("\n -----===== INACTIVE QUEUES =====-----\n");
|
||||
for(Queue<String> inactiveQueues : getInactiveQueuesByPrecedence().values()) {
|
||||
@@ -1288,7 +1315,7 @@ implements Closeable,
|
||||
if(obj instanceof WorkQueue) {
|
||||
q = (WorkQueue)obj;
|
||||
} else if (obj instanceof DelayedWorkQueue) {
|
||||
q = ((DelayedWorkQueue)obj).getWorkQueue();
|
||||
q = ((DelayedWorkQueue)obj).getWorkQueue(this);
|
||||
} else {
|
||||
try {
|
||||
q = this.allQueues.get((String)obj);
|
||||
@@ -1316,7 +1343,7 @@ implements Closeable,
|
||||
int allCount = allQueues.size();
|
||||
int inProcessCount = inProcessQueues.uniqueSet().size();
|
||||
int readyCount = readyClassQueues.size();
|
||||
int snoozedCount = snoozedClassQueues.size();
|
||||
int snoozedCount = getSnoozedCount();
|
||||
int activeCount = inProcessCount + readyCount + snoozedCount;
|
||||
int inactiveCount = getTotalInactiveQueues();
|
||||
int retiredCount = getRetiredQueues().size();
|
||||
@@ -1413,16 +1440,10 @@ implements Closeable,
|
||||
this.readyClassQueues.size(), REPORT_MAX_QUEUES);
|
||||
|
||||
w.print("\n -----===== SNOOZED QUEUES =====-----\n");
|
||||
Transformer<DelayedWorkQueue,WorkQueue> ter =
|
||||
new Transformer<DelayedWorkQueue, WorkQueue>() {
|
||||
public WorkQueue transform(DelayedWorkQueue dwq) {
|
||||
return dwq.getWorkQueue();
|
||||
}
|
||||
};
|
||||
Transform<DelayedWorkQueue, WorkQueue> t =
|
||||
new Transform<DelayedWorkQueue,WorkQueue>(snoozedClassQueues, ter);
|
||||
copy = extractSome(t, REPORT_MAX_QUEUES);
|
||||
appendQueueReports(w, "SNOOZED", copy.iterator(), copy.size(), REPORT_MAX_QUEUES);
|
||||
Object[] objs = snoozedClassQueues.toArray();
|
||||
DelayedWorkQueue[] qs = Arrays.copyOf(objs,objs.length,DelayedWorkQueue[].class);
|
||||
Arrays.sort(qs);
|
||||
appendQueueReports(w, "SNOOZED", new ObjectArrayIterator(qs), getSnoozedCount(), REPORT_MAX_QUEUES);
|
||||
|
||||
w.print("\n -----===== INACTIVE QUEUES =====-----\n");
|
||||
SortedMap<Integer,Queue<String>> sortedInactives = getInactiveQueuesByPrecedence();
|
||||
@@ -1478,22 +1499,28 @@ implements Closeable,
|
||||
int total, int max) {
|
||||
Object obj;
|
||||
WorkQueue q;
|
||||
for(int count = 0; iterator.hasNext() && (count < max); count++) {
|
||||
int count;
|
||||
for(count = 0; iterator.hasNext() && (count < max); count++) {
|
||||
obj = iterator.next();
|
||||
if (obj == null) {
|
||||
continue;
|
||||
}
|
||||
q = (obj instanceof WorkQueue)
|
||||
? (WorkQueue)obj
|
||||
: this.allQueues.get((String)obj);
|
||||
if(obj instanceof WorkQueue) {
|
||||
q = (WorkQueue)obj;
|
||||
} else if (obj instanceof DelayedWorkQueue) {
|
||||
q = (WorkQueue)((DelayedWorkQueue)obj).getWorkQueue(this);
|
||||
} else {
|
||||
q = this.allQueues.get((String)obj);
|
||||
}
|
||||
if(q == null) {
|
||||
w.print("WARNING: No report for queue "+obj);
|
||||
}
|
||||
w.println(label+"#"+count+":");
|
||||
q.reportTo(w);
|
||||
}
|
||||
if(total > max) {
|
||||
w.print("...and " + (total - max) + " more "+label+".\n");
|
||||
count++;
|
||||
if(count < total) {
|
||||
w.print("...and " + (total - count) + " more "+label+".\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1550,19 +1577,24 @@ implements Closeable,
|
||||
}
|
||||
int inProcessCount = inProcessQueues.uniqueSet().size();
|
||||
int readyCount = readyClassQueues.size();
|
||||
int snoozedCount = snoozedClassQueues.size();
|
||||
int snoozedCount = getSnoozedCount();
|
||||
int activeCount = inProcessCount + readyCount + snoozedCount;
|
||||
int inactiveCount = getTotalInactiveQueues();
|
||||
int totalQueueCount = (activeCount+inactiveCount);
|
||||
return (totalQueueCount == 0) ? 0 : queuedUriCount.get() / totalQueueCount;
|
||||
}
|
||||
|
||||
protected int getSnoozedCount() {
|
||||
return snoozedClassQueues.size() + snoozedOverflowCount.get();
|
||||
}
|
||||
|
||||
public float congestionRatio() {
|
||||
if(inProcessQueues==null || readyClassQueues==null || snoozedClassQueues==null) {
|
||||
return 0;
|
||||
}
|
||||
int inProcessCount = inProcessQueues.uniqueSet().size();
|
||||
int readyCount = readyClassQueues.size();
|
||||
int snoozedCount = snoozedClassQueues.size();
|
||||
int snoozedCount = getSnoozedCount();
|
||||
int activeCount = inProcessCount + readyCount + snoozedCount;
|
||||
int eligibleInactiveCount = getTotalEligibleInactiveQueues();
|
||||
return (float)(activeCount + eligibleInactiveCount) / (inProcessCount + snoozedCount);
|
||||
@@ -1592,105 +1624,5 @@ implements Closeable,
|
||||
return inProcessQueues.size();
|
||||
}
|
||||
|
||||
class DelayedWorkQueue implements Delayed, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public String classKey;
|
||||
public long wakeTime;
|
||||
|
||||
/**
|
||||
* Something can become a WorkQueue instance. This can be three things:
|
||||
*
|
||||
* <ol>
|
||||
* <li>null, if this DelayedWorkQueue instance was recently
|
||||
* deserialized;
|
||||
* <li>A SoftReference<WorkQueue>, if the WorkQueue's waitTime
|
||||
* exceeded SNOOZE_LONG_MS
|
||||
* <li>A hard WorkQueue reference, if the WorkQueue's waitTime did not
|
||||
* exceed SNOOZE_LONG_MS. Idea here is that we thought we
|
||||
* needed the WorkQueue soon and didn't want to risk losing the
|
||||
* instance.
|
||||
* </ol>
|
||||
*
|
||||
* The {@link #getWorkQueue()} method figures out what to return in
|
||||
* all three of the above cases.
|
||||
*/
|
||||
private transient Object workQueue;
|
||||
|
||||
public DelayedWorkQueue(WorkQueue queue) {
|
||||
this.classKey = queue.getClassKey();
|
||||
this.wakeTime = queue.getWakeTime();
|
||||
|
||||
this.workQueue = queue;
|
||||
}
|
||||
|
||||
private void setWorkQueue(WorkQueue queue) {
|
||||
long wakeTime = queue.getWakeTime();
|
||||
long delay = wakeTime - System.currentTimeMillis();
|
||||
if (delay > getSnoozeLongMs()) {
|
||||
this.workQueue = new SoftReference<WorkQueue>(queue);
|
||||
} else {
|
||||
this.workQueue = queue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public WorkQueue getWorkQueue() {
|
||||
if (workQueue == null) {
|
||||
// This is a recently deserialized DelayedWorkQueue instance
|
||||
WorkQueue result = getQueueFor(classKey);
|
||||
setWorkQueue(result);
|
||||
return result;
|
||||
}
|
||||
if (workQueue instanceof SoftReference) {
|
||||
@SuppressWarnings("unchecked")
|
||||
SoftReference<WorkQueue> ref = (SoftReference)workQueue;
|
||||
WorkQueue result = ref.get();
|
||||
if (result == null) {
|
||||
result = getQueueFor(classKey);
|
||||
}
|
||||
setWorkQueue(result);
|
||||
return result;
|
||||
}
|
||||
return (WorkQueue)workQueue;
|
||||
}
|
||||
|
||||
public long getDelay(TimeUnit unit) {
|
||||
return unit.convert(
|
||||
wakeTime - System.currentTimeMillis(),
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public String getClassKey() {
|
||||
return classKey;
|
||||
}
|
||||
|
||||
public long getWakeTime() {
|
||||
return wakeTime;
|
||||
}
|
||||
|
||||
public void setWakeTime(long time) {
|
||||
this.wakeTime = time;
|
||||
}
|
||||
|
||||
public int compareTo(Delayed obj) {
|
||||
if (this == obj) {
|
||||
return 0; // for exact identity only
|
||||
}
|
||||
DelayedWorkQueue other = (DelayedWorkQueue) obj;
|
||||
if (wakeTime > other.getWakeTime()) {
|
||||
return 1;
|
||||
}
|
||||
if (wakeTime < other.getWakeTime()) {
|
||||
return -1;
|
||||
}
|
||||
// at this point, the ordering is arbitrary, but still
|
||||
// must be consistent/stable over time
|
||||
return this.classKey.compareTo(other.getClassKey());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user