/******************************************************************************* * 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen */ public class SessionGarbageCollectorJob extends Job { private static final Logger LOGGER = LoggerFactory.getLogger(SessionGarbageCollectorJob.class); 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(); LOGGER.info("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) { LOGGER.info("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) LOGGER.info("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"); LOGGER.info("Session.GC " + (intermediate - begin) + "ms"); } // Reschedule after a quiet period. return Status.OK_STATUS; } finally { schedule(interval); monitor.done(); } } }