All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jetbrains.exodus.gc.BackgroundCleaningJob.kt Maven / Gradle / Ivy

There is a newer version: 9.8.0.76914
Show newest version
/**
 * Copyright 2010 - 2022 JetBrains s.r.o.
 *
 * Licensed 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
 *
 * https://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 jetbrains.exodus.gc

import jetbrains.exodus.log.Log
import java.util.*

internal class BackgroundCleaningJob(gc: GarbageCollector) : GcJob(gc) {

    private val beforeGcActions = ArrayList()

    override fun getName() = "Background cleaner"

    fun addBeforeGcAction(action: Runnable) {
        beforeGcActions += action
    }

    override fun doJob() {
        val gc = this.gc ?: return
        val cleaner = gc.cleaner
        if (!cleaner.isCurrentThread) {
            val processor = cleaner.getJobProcessor()
            GarbageCollector.loggingInfo { "Re-queueing BackgroundCleaningJob[${gc.environment.location}] to thread[${cleaner.threadId}]" }
            reQueue(processor)
            return
        }

        // is invoked too early?
        val minTimeToInvokeCleaner = gc.startTime
        val currentTime = System.currentTimeMillis()
        if (minTimeToInvokeCleaner > currentTime) {
            wakeAt(gc, minTimeToInvokeCleaner)
            return
        }

        val env = gc.environment
        val ec = env.environmentConfig
        val gcRunPeriod = ec.gcRunPeriod

        // is invoked too often?
        if (gcRunPeriod > 0 && gc.lastInvocationTime + gcRunPeriod > currentTime) {
            wakeAt(gc, gc.lastInvocationTime + gcRunPeriod)
            return
        }

        val log = env.log
        // are there enough files in the log?
        if (gc.minFileAge < log.numberOfFiles) {
            if (!canContinue()) {
                wakeAt(gc, System.currentTimeMillis() + gcRunPeriod)
                return
            }
            cleaner.isCleaning = true
            try {
                doCleanLog(log, gc)
                if (gc.isTooMuchFreeSpace) {
                    if (gcRunPeriod > 0) {
                        wakeAt(gc, System.currentTimeMillis() + gcRunPeriod)
                    }
                }
            } finally {
                gc.lastInvocationTime = System.currentTimeMillis()
                cleaner.isCleaning = false
            }
        }
    }

    private fun doCleanLog(log: Log, gc: GarbageCollector) {

        GarbageCollector.loggingInfo { "Executing before GC actions for ${log.location}" }
        try {
            beforeGcActions.forEach { it.run() }
        } catch (t: Throwable) {
            GarbageCollector.loggingError(t) { "Failed to execute before GC actions for ${log.location}" }
        }

        val up = gc.utilizationProfile

        GarbageCollector.loggingInfo { "Starting background cleaner loop for ${log.location}, free space: ${up.totalFreeSpacePercent()}%" }

        val env = gc.environment
        val highFile = log.highFileAddress
        val loopStart = System.currentTimeMillis()
        val gcRunPeriod = env.environmentConfig.gcRunPeriod

        try {
            do {
                val fragmentedFiles = up.getFilesSortedByUtilization(highFile)
                if (!fragmentedFiles.hasNext()) {
                    break
                }
                if (!cleanFiles(gc, fragmentedFiles)) {
                    Thread.yield()
                }
            } while (canContinue() && loopStart + gcRunPeriod > System.currentTimeMillis())
        } finally {
            up.estimateTotalBytes()
            up.isDirty = true
            GarbageCollector.loggingInfo { "Finished background cleaner loop for ${log.location}, free space: ${up.totalFreeSpacePercent()}%" }
        }
    }

    /**
     * We need this synchronized method in order to provide correctness of  [BackgroundCleaner.suspend].
     */
    @Synchronized
    private fun cleanFiles(gc: GarbageCollector, fragmentedFiles: Iterator) = gc.cleanFiles(fragmentedFiles)

    private fun canContinue(): Boolean {
        val gc = this.gc ?: return false
        val cleaner = gc.cleaner
        if (cleaner.isSuspended || cleaner.isFinished) {
            return false
        }
        val ec = gc.environment.environmentConfig
        return ec.isGcEnabled && !ec.envIsReadonly && gc.isTooMuchFreeSpace
    }

    private fun wakeAt(gc: GarbageCollector, time: Long) {
        GarbageCollector.loggingDebug {
            val location = gc.environment.location
            "Queueing BackgroundCleaningJob[$location] to wake up at [${Date(time)}]"
        }
        gc.wakeAt(time)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy