Add ingest cancellation reasons; fix job startup bug

This commit is contained in:
Richard Cordovano 2015-11-22 18:12:14 -05:00
parent 2bc998155f
commit 4d19cf1e83
5 changed files with 159 additions and 41 deletions

View File

@ -73,6 +73,7 @@ import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.core.UserPreferencesException;
import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
@ -327,7 +328,7 @@ public class Case implements SleuthkitCase.ErrorObserver {
SwingUtilities.invokeLater(() -> {
WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
});
IngestManager.getInstance().cancelAllIngestJobs();
IngestManager.getInstance().cancelAllIngestJobs(IngestJob.CancellationReason.CASE_CLOSED);
doCaseChange(null); //closes windows, etc
if (null != oldCase.tskErrorReporter) {
oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case

View File

@ -118,6 +118,8 @@ final class DataSourceIngestJob {
*/
private volatile boolean currentDataSourceIngestModuleCancelled;
private volatile boolean cancelled;
private volatile IngestJob.CancellationReason cancellationReason = IngestJob.CancellationReason.NOT_CANCELLED;
private final Object cancellationStateMonitor = new Object();
private final List<String> cancelledDataSourceIngestModules = new CopyOnWriteArrayList<>();
/**
@ -160,12 +162,12 @@ final class DataSourceIngestJob {
* Constructs an object that encapsulates a data source and the ingest
* module pipelines used to process it.
*
* @param parentJob The ingest job of which this data source ingest job is a
* part.
* @param dataSource The data source to be ingested.
* @param settings The settings for the ingest job.
* @param parentJob The ingest job of which this data source ingest
* job is a part.
* @param dataSource The data source to be ingested.
* @param settings The settings for the ingest job.
* @param runInteractively Whether or not this job should use NetBeans
* progress handles.
* progress handles.
*/
DataSourceIngestJob(IngestJob parentJob, Content dataSource, IngestJobSettings settings, boolean runInteractively) {
this.parentJob = parentJob;
@ -252,12 +254,12 @@ final class DataSourceIngestJob {
* collection as they are added to the output collection.
*
* @param ingestModuleTemplates A mapping of ingest module factory class
* names to ingest module templates.
* @param pipelineConfig An ordered list of ingest module factory class
* names representing an ingest pipeline.
* names to ingest module templates.
* @param pipelineConfig An ordered list of ingest module factory
* class names representing an ingest pipeline.
*
* @return An ordered list of ingest module templates, i.e., an
* uninstantiated pipeline.
* uninstantiated pipeline.
*/
private static List<IngestModuleTemplate> getConfiguredIngestModuleTemplates(Map<String, IngestModuleTemplate> ingestModuleTemplates, List<String> pipelineConfig) {
List<IngestModuleTemplate> templates = new ArrayList<>();
@ -498,7 +500,7 @@ final class DataSourceIngestJob {
String dialogTitle = NbBundle.getMessage(DataSourceIngestJob.this.getClass(), "IngestJob.cancellationDialog.title");
JOptionPane.showConfirmDialog(null, panel, dialogTitle, JOptionPane.OK_OPTION, JOptionPane.PLAIN_MESSAGE);
if (panel.cancelAllDataSourceIngestModules()) {
DataSourceIngestJob.this.cancel();
DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
} else {
DataSourceIngestJob.this.cancelCurrentDataSourceIngestModule();
}
@ -527,7 +529,7 @@ final class DataSourceIngestJob {
// the cancel button on the progress bar and the OK button
// of a cancelation confirmation dialog supplied by
// NetBeans.
DataSourceIngestJob.this.cancel();
DataSourceIngestJob.this.cancel(IngestJob.CancellationReason.USER_CANCELLED);
return true;
}
});
@ -672,8 +674,9 @@ final class DataSourceIngestJob {
* @param task A file ingest task.
*
* @throws InterruptedException if the thread executing this code is
* interrupted while blocked on taking from or putting to the file ingest
* pipelines collection.
* interrupted while blocked on taking from or
* putting to the file ingest pipelines
* collection.
*/
void process(FileIngestTask task) throws InterruptedException {
try {
@ -774,7 +777,7 @@ final class DataSourceIngestJob {
* process the data source is known.
*
* @param workUnits Total number of work units for the processing of the
* data source.
* data source.
*/
void switchDataSourceIngestProgressBarToDeterminate(int workUnits) {
if (this.doUI && !this.cancelled) {
@ -839,7 +842,7 @@ final class DataSourceIngestJob {
* mode. The task name is the "subtitle" under the display name.
*
* @param currentTask The task name.
* @param workUnits Number of work units performed.
* @param workUnits Number of work units performed.
*/
void advanceDataSourceIngestProgressBar(String currentTask, int workUnits) {
if (this.doUI && !this.cancelled) {
@ -910,8 +913,10 @@ final class DataSourceIngestJob {
/**
* Requests cancellation of ingest, i.e., a shutdown of the data source
* level and file level ingest pipelines.
*
* @param reason The cancellation reason.
*/
void cancel() {
void cancel(IngestJob.CancellationReason reason) {
if (this.doUI) {
/**
* Put a cancellation message on data source level ingest progress
@ -951,7 +956,14 @@ final class DataSourceIngestJob {
}
}
this.cancelled = true;
synchronized (cancellationStateMonitor) {
/*
* These fields are volatile for reading, synchronized on the
* monitor here for writing.
*/
this.cancelled = true;
this.cancellationReason = reason;
}
/**
* Tell the task scheduler to cancel all pending tasks, i.e., tasks not
@ -964,9 +976,9 @@ final class DataSourceIngestJob {
/**
* Set the current module name being run and the file name it is running on.
* To be used for more detailed cancelling.
*
*
* @param moduleName Name of module currently running.
* @param taskName Name of file the module is running on.
* @param taskName Name of file the module is running on.
*/
void setCurrentFileIngestModule(String moduleName, String taskName) {
this.currentFileIngestModule = moduleName;
@ -983,6 +995,15 @@ final class DataSourceIngestJob {
return this.cancelled;
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
IngestJob.CancellationReason getCancellationReason() {
return this.cancellationReason;
}
/**
* Write ingest module errors to the log.
*
@ -1019,6 +1040,7 @@ final class DataSourceIngestJob {
private final long estimatedFilesToProcess;
private final IngestTasksScheduler.IngestJobTasksSnapshot tasksSnapshot;
private final boolean jobCancelled;
private final IngestJob.CancellationReason jobCancellationReason;
private final List<String> cancelledDataSourceModules;
/**
@ -1047,6 +1069,7 @@ final class DataSourceIngestJob {
}
this.jobCancelled = cancelled;
this.jobCancellationReason = cancellationReason;
this.cancelledDataSourceModules = new ArrayList<>(DataSourceIngestJob.this.cancelledDataSourceIngestModules);
if (getIngestTasksSnapshot) {
@ -1069,7 +1092,7 @@ final class DataSourceIngestJob {
* Gets time these statistics were collected.
*
* @return The statistics collection time as number of milliseconds
* since January 1, 1970, 00:00:00 GMT.
* since January 1, 1970, 00:00:00 GMT.
*/
long getSnapshotTime() {
return snapShotTime;
@ -1099,7 +1122,7 @@ final class DataSourceIngestJob {
* Gets the time the ingest job was started.
*
* @return The start time as number of milliseconds since January 1,
* 1970, 00:00:00 GMT.
* 1970, 00:00:00 GMT.
*/
long getJobStartTime() {
return jobStartTime;
@ -1185,12 +1208,21 @@ final class DataSourceIngestJob {
return this.jobCancelled;
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
IngestJob.CancellationReason getCancellationReason() {
return this.jobCancellationReason;
}
/**
* Gets a list of the display names of any canceled data source level
* ingest modules
*
* @return A list of canceled data source level ingest module display
* names, possibly empty.
* names, possibly empty.
*/
List<String> getCancelledDataSourceIngestModules() {
return Collections.unmodifiableList(this.cancelledDataSourceModules);

View File

@ -37,12 +37,26 @@ import org.sleuthkit.datamodel.Content;
*/
public final class IngestJob {
/*
* An ingest job can be cancelled for various reasons.
*/
public enum CancellationReason {
NOT_CANCELLED,
USER_CANCELLED,
INGEST_MODULES_STARTUP_FAILED,
OUT_OF_DISK_SPACE,
SERVICES_DOWN,
CASE_CLOSED
}
private static final AtomicLong nextId = new AtomicLong(0L);
private final long id;
private final Map<Long, DataSourceIngestJob> dataSourceJobs;
private final AtomicInteger incompleteJobsCount;
private boolean started; // Guarded by this
private volatile boolean cancelled;
private boolean started;
private boolean cancelled;
private CancellationReason cancellationReason;
/**
* Constructs an ingest job that runs a collection of data sources through a
@ -61,6 +75,7 @@ public final class IngestJob {
this.dataSourceJobs.put(dataSourceIngestJob.getId(), dataSourceIngestJob);
}
incompleteJobsCount = new AtomicInteger(dataSourceJobs.size());
cancellationReason = CancellationReason.NOT_CANCELLED;
}
/**
@ -108,19 +123,32 @@ public final class IngestJob {
}
started = true;
List<DataSourceIngestJob> startedDataSourceJobs = new ArrayList<>();
/*
* Try to start each data source ingest job. Note that there is a not
* unwarranted assumption here that if there is going to be a module
* startup failure, it will be for the first data source ingest job.
*
* TODO (RC): Consider separating module start up from pipeline startup
* so that no processing is done if this assumption is false.
*/
for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) {
errors.addAll(dataSourceJob.start());
if (errors.isEmpty()) {
IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource());
startedDataSourceJobs.add(dataSourceJob);
} else {
startedDataSourceJobs.stream().forEach((startedDataSourceJob) -> {
startedDataSourceJob.cancel();
});
if (errors.isEmpty() == false) {
break;
}
}
/*
* Handle start up success or failure.
*/
if (errors.isEmpty()) {
for (DataSourceIngestJob dataSourceJob : this.dataSourceJobs.values()) {
IngestManager.getInstance().fireDataSourceAnalysisStarted(id, dataSourceJob.getId(), dataSourceJob.getDataSource());
}
} else {
cancel(CancellationReason.INGEST_MODULES_STARTUP_FAILED);
}
return errors;
}
@ -161,13 +189,37 @@ public final class IngestJob {
* unfinished tasks and stopping the ingest pipelines. Returns immediately,
* but there may be a delay before all of the ingest modules in the
* pipelines respond by stopping processing.
*
* @deprecated Use cancel(CancellationReason reason) instead
*/
@Deprecated
synchronized public void cancel() {
IngestManager ingestManager = IngestManager.getInstance();
cancel(CancellationReason.USER_CANCELLED);
}
/**
* Requests cancellation of this ingest job, which means discarding
* unfinished tasks and stopping the ingest pipelines. Returns immediately,
* but there may be a delay before all of the ingest modules in the
* pipelines respond by stopping processing.
*
* @param reason The reason for cancellation.
*/
synchronized public void cancel(CancellationReason reason) {
this.dataSourceJobs.values().stream().forEach((job) -> {
job.cancel();
job.cancel(reason);
});
this.cancelled = true;
this.cancellationReason = reason;
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return this.cancellationReason;
}
/**
@ -176,7 +228,7 @@ public final class IngestJob {
*
* @return True or false.
*/
public boolean isCancelled() {
synchronized public boolean isCancelled() {
return this.cancelled;
}
@ -208,6 +260,7 @@ public final class IngestJob {
private boolean fileIngestRunning;
private Date fileIngestStartTime;
private final boolean jobCancelled;
private final IngestJob.CancellationReason jobCancellationReason;
/**
* A snapshot of the progress of an ingest job on the processing of a
@ -241,6 +294,15 @@ public final class IngestJob {
return snapshot.isCancelled();
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return snapshot.getCancellationReason();
}
/**
* Gets a list of the display names of any canceled data source
* level ingest modules.
@ -280,6 +342,7 @@ public final class IngestJob {
}
}
this.jobCancelled = cancelled;
this.jobCancellationReason = cancellationReason;
}
/**
@ -312,8 +375,8 @@ public final class IngestJob {
}
/**
* Queries whether or not a cancellation request had been issued at the
* time the snapshot was taken.
* Queries whether or not an ingest job level cancellation request had
* been issued at the time the snapshot was taken.
*
* @return True or false.
*/
@ -321,6 +384,15 @@ public final class IngestJob {
return this.jobCancelled;
}
/**
* Gets the reason this job was cancelled.
*
* @return The cancellation reason, may be not cancelled.
*/
synchronized public CancellationReason getCancellationReason() {
return this.jobCancellationReason;
}
/**
* Gets snapshots of the progress processing individual data sources.
*

View File

@ -357,7 +357,7 @@ public class IngestManager {
}
// cancel ingest if running
cancelAllIngestJobs();
cancelAllIngestJobs(IngestJob.CancellationReason.SERVICES_DOWN);
}
}
};
@ -627,8 +627,21 @@ public class IngestManager {
/**
* Cancels all ingest jobs in progress.
*
* @deprecated Use cancelAllIngestJobs(IngestJob.CancellationReason reason)
* instead.
*/
@Deprecated
public synchronized void cancelAllIngestJobs() {
cancelAllIngestJobs(IngestJob.CancellationReason.USER_CANCELLED);
}
/**
* Cancels all ingest jobs in progress.
*
* @param reason The cancellation reason.
*/
public synchronized void cancelAllIngestJobs(IngestJob.CancellationReason reason) {
// Stop creating new ingest jobs.
for (Future<Void> handle : ingestJobStarters.values()) {
handle.cancel(true);
@ -636,7 +649,7 @@ public class IngestManager {
// Cancel all the jobs already created.
for (IngestJob job : this.jobsById.values()) {
job.cancel();
job.cancel(reason);
}
}

View File

@ -196,7 +196,7 @@ public final class IngestMonitor {
/*
* Shut down ingest by cancelling all ingest jobs.
*/
manager.cancelAllIngestJobs();
manager.cancelAllIngestJobs(IngestJob.CancellationReason.OUT_OF_DISK_SPACE);
String diskPath = root.getAbsolutePath();
IngestServices.getInstance().postMessage(IngestMessage.createManagerErrorMessage(
NbBundle.getMessage(this.getClass(), "IngestMonitor.mgrErrMsg.lowDiskSpace.title", diskPath),