blob: 90c1c2aae0126c6e6703e0d48264264be2f1609c [file] [log] [blame]
/* $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.jobs;
import java.util.*;
import java.io.*;
/** Debugging class to keep track of recent modifications to the jobqueue table,
* along with context as to where it occurred. If a jobqueue state error occurs,
* we can then print out all of the pertinent history and find the culprit.
*/
public class TrackerClass
{
// The goal of this class is to keep track of at least some of the history
// potentially affecting each record.
protected final static long HISTORY_LENGTH = 60000L * 15; // 15 minutes
// Active transaction
protected final static Map<String,TransactionData> transactionData = new HashMap<String,TransactionData>();
// Modification history
protected final static List<TransactionData> history = new ArrayList<TransactionData>();
// Place where we keep track of individual modifications
private TrackerClass()
{
}
/** Add a single record event, as yet uncommitted */
public static void noteRecordEvent(Long recordID, int newStatus, String description)
{
addEvent(new RecordEvent(recordID, newStatus, new Exception(description)));
}
/** Add a global event, as yet uncommitted, which has the potential
* to affect any record's state in a given job.
*/
public static void noteJobEvent(Long jobID, String description)
{
addEvent(new JobEvent(jobID, new Exception(description)));
}
/** Add a global event, as yet uncommitted, which has the potential
* to affect the state of any record.
*/
public static void noteGlobalEvent(String description)
{
addEvent(new GlobalEvent(new Exception(description)));
}
protected static void addEvent(HistoryRecord hr)
{
String threadName = Thread.currentThread().getName();
TransactionData td;
synchronized (transactionData)
{
td = transactionData.get(threadName);
if (td == null)
{
td = new TransactionData();
transactionData.put(threadName,td);
}
}
td.addEvent(hr);
}
/** Note a commit operation.
*/
public static void noteCommit()
{
long currentTime = System.currentTimeMillis();
String threadName = Thread.currentThread().getName();
TransactionData td;
synchronized (transactionData)
{
td = transactionData.get(threadName);
transactionData.remove(threadName);
}
if (td == null)
return;
// Only keep stuff around for an hour
long removalCutoff = currentTime - HISTORY_LENGTH;
synchronized (history)
{
history.add(td);
// Clean out older records
while (history.size() > 0)
{
TransactionData td2 = history.get(0);
if (td2.isFlushable(removalCutoff))
history.remove(0);
else
break;
}
}
}
/** Note a rollback operation.
*/
public static void noteRollback()
{
String threadName = Thread.currentThread().getName();
synchronized (transactionData)
{
transactionData.remove(threadName);
}
}
public static void printForensics(Long recordID, int existingStatus)
{
synchronized (transactionData)
{
synchronized (history)
{
System.err.println("---- Forensics for record "+recordID+", current status: "+existingStatus+" ----");
System.err.println("--Current stack trace--");
StringWriter sw = new StringWriter();
new Exception("Unexpected jobqueue status").printStackTrace(new PrintWriter(sw,true));
System.err.print(sw.toString());
System.err.println("--Active transactions--");
for (String threadName : transactionData.keySet())
{
for (HistoryRecord hr : transactionData.get(threadName).getEvents())
{
if (hr.applies(recordID))
{
System.err.println("Thread '"+threadName+"' was active:");
hr.print();
}
}
}
System.err.println("--Pertinent History--");
for (TransactionData td : history)
{
for (HistoryRecord hr : td.getEvents())
{
if (hr.applies(recordID))
{
hr.print();
}
}
}
}
}
}
protected static class TransactionData
{
protected final List<HistoryRecord> transactionEvents = new ArrayList<HistoryRecord>();
protected long timestamp;
public TransactionData()
{
timestamp = System.currentTimeMillis();
}
public void addEvent(HistoryRecord event)
{
transactionEvents.add(event);
}
public List<HistoryRecord> getEvents()
{
return transactionEvents;
}
public boolean isFlushable(long cutoffTime)
{
return cutoffTime > timestamp;
}
}
protected abstract static class HistoryRecord
{
protected long timestamp;
protected Exception trace;
public HistoryRecord(Exception trace)
{
this.trace = trace;
this.timestamp = System.currentTimeMillis();
}
public void print()
{
System.err.println(" at "+new Long(timestamp)+", location: ");
StringWriter sw = new StringWriter();
trace.printStackTrace(new PrintWriter(sw,true));
System.err.print(sw.toString());
}
public abstract boolean applies(Long recordID);
}
protected static class RecordEvent extends HistoryRecord
{
protected Long recordID;
protected int newStatus;
public RecordEvent(Long recordID, int newStatus, Exception trace)
{
super(trace);
this.recordID = recordID;
this.newStatus = newStatus;
}
@Override
public void print()
{
System.err.println("Record "+recordID+" status modified to "+newStatus);
super.print();
}
@Override
public boolean applies(Long recordID)
{
return this.recordID.equals(recordID);
}
}
protected static class JobEvent extends HistoryRecord
{
protected Long jobID;
public JobEvent(Long jobID, Exception trace)
{
super(trace);
this.jobID = jobID;
}
@Override
public void print()
{
System.err.println("All job related records modified for job "+jobID);
super.print();
}
@Override
public boolean applies(Long recordID)
{
return true;
}
}
protected static class GlobalEvent extends HistoryRecord
{
public GlobalEvent(Exception trace)
{
super(trace);
}
@Override
public void print()
{
System.err.println("All records modified");
super.print();
}
@Override
public boolean applies(Long recordID)
{
return true;
}
}
}