/*******************************************************************************
* Copyright (c) 2007, 2011 Association for Decentralized Information Management
* in Industry THTH ry.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* VTT Technical Research Centre of Finland - initial API and implementation
*******************************************************************************/
package org.simantics.ui.jobs;
import java.util.function.Consumer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.simantics.DatabaseJob;
import org.simantics.Simantics;
import org.simantics.db.Session;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.layer0.util.SessionGarbageCollection;
import org.simantics.db.service.LifecycleSupport;
import org.simantics.utils.ui.ErrorLogger;
/**
* @author Tuukka Lehtonen
*/
public class SessionGarbageCollectorJob extends Job {
private static SessionGarbageCollectorJob instance;
public synchronized static SessionGarbageCollectorJob getInstance() {
if (instance == null)
instance = new SessionGarbageCollectorJob();
return instance;
}
private static final boolean TRACE = false;
/**
* At least 60 seconds between executions.
*/
private static final long DEFAULT_QUIET_TIME = 5000;
private long start;
private long quietTime;
private long userDefinedQuietTime;
private boolean enabled = true;
/**
*
*/
public SessionGarbageCollectorJob() {
this(DEFAULT_QUIET_TIME);
}
/**
* @param delayBetweenExecutions
*/
public SessionGarbageCollectorJob(long delayBetweenExecutions) {
super("Database Garbage Collector");
setPriority(Job.DECORATE);
// Hide job from users.
setSystem(true);
this.start = System.currentTimeMillis();
this.quietTime = delayBetweenExecutions;
this.userDefinedQuietTime = delayBetweenExecutions;
}
/**
* Cancels the currently scheduled execution of this job and reschedules
* another execution after the current quiet time. This can be used for
* easily pushing GC back a bit while performing an operation during which
* it is not efficient to GC.
*
* @see #setQuietTime(long)
*/
public void rescheduleAfterQuietTime() {
cancel();
scheduleAfterQuietTime();
}
/**
* Cancels the currently scheduled execution of this job and reschedules
* another with no delay. This can be used for immediate forced execution of
* session GC.
*/
public void rescheduleNow() {
cancel();
schedule();
}
/**
* @param enabled
* @return
*/
public SessionGarbageCollectorJob setEnabled(boolean enabled) {
this.enabled = enabled;
return this;
}
// /**
// * @param quietTime quiet time between collections in
// * milliseconds
// */
// public SessionGarbageCollectorJob setQuietTime(long quietTime) {
// this.quietTime = quietTime;
// return this;
// }
/**
* Schedules this job after the currently set quiet time.
*
* @see #setQuietTime(long)
*/
public void scheduleAfterQuietTime() {
schedule(quietTime);
}
@Override
public boolean shouldSchedule() {
return enabled;
}
@Override
public boolean shouldRun() {
if (TRACE) {
if (!enabled) {
long t = System.currentTimeMillis();
System.out.println("GC disabled, not running @ " + ((double) (t - start) * 1e-3) + " seconds");
}
}
return enabled;
}
/**
* Only invoked with actual errors, not null
values.
*/
Consumer errorCallback = e -> ErrorLogger.defaultLogError(e);
@Override
protected IStatus run(IProgressMonitor monitor) {
long interval = quietTime;
try {
Session session = Simantics.peekSession();
if (session == null)
return Status.CANCEL_STATUS;
LifecycleSupport lfs = session.peekService(LifecycleSupport.class);
if (lfs == null || lfs.isClosed() || lfs.isClosing())
return Status.CANCEL_STATUS;
// Never run while a heavy database job is in progress.
if (DatabaseJob.inProgress()) {
// Schedule again in at most 10 seconds instead of
// waiting for the whole quiet time period again.
interval = Math.min(10, quietTime);
return Status.CANCEL_STATUS;
}
// // Don't run if there are currently write requests in progress
// TransactionSupport ts = session.getService(TransactionSupport.class);
// if (ts.getWriteCount() > 0) {
// if (TRACE) {
// long t = System.currentTimeMillis();
// System.out.println("Write in progress, no GC @ " + ((double) (t - start) * 1e-3) + " seconds");
// }
// return Status.CANCEL_STATUS;
// }
long begin = System.currentTimeMillis();
if (TRACE) {
System.out.println("running GC @ " + ((double) (begin - start) * 1e-3) + " seconds");
}
boolean busy = SessionGarbageCollection.gc(monitor, session, true, errorCallback);
if(busy) {
quietTime = Math.max((3*quietTime)/4, 100);
} else {
if(quietTime < userDefinedQuietTime)
quietTime = Math.min(userDefinedQuietTime, (long)(1.2*quietTime));
}
if (TRACE)
if(busy)
System.err.println("Session GC ended busy. New quiet time is " + quietTime);
// This quiet time might have been adjusted in GC
interval = quietTime;
long intermediate = System.currentTimeMillis();
// Clean up heap of all garbage left behind by SessionGarbageCollection
//System.gc();
if (TRACE) {
//long end = System.currentTimeMillis();
//System.out.println("Session.GC " + (intermediate - begin) + "ms, System.GC " + (end-intermediate) + "ms, total " + (end - begin) + "ms");
System.out.println("Session.GC " + (intermediate - begin) + "ms");
}
// Reschedule after a quiet period.
return Status.OK_STATUS;
} finally {
schedule(interval);
monitor.done();
}
}
}