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

jetbrains.exodus.log.replication.LogAppender.kt Maven / Gradle / Ivy

/**
 * Copyright 2010 - 2020 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
 *
 * 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 jetbrains.exodus.log.replication

import jetbrains.exodus.ExodusException
import jetbrains.exodus.log.Log
import jetbrains.exodus.log.LogTip
import mu.KLogging
import org.jetbrains.annotations.NotNull

object LogAppender : KLogging() {

    @JvmStatic
    @JvmOverloads
    fun appendLog(log: Log, delta: LogReplicationDelta, fileFactory: FileFactory, currentTip: LogTip = log.beginWrite()): () -> LogTip {
        try {
            checkPreconditions(log, currentTip, delta)

            val highAddress = delta.highAddress
            if (highAddress < delta.startAddress) {
                throw IllegalArgumentException("Cannot decrease high address")
            }
            if (delta.files.isEmpty()) {
                // truncate log
                log.abortWrite()
                return {
                    log.setHighAddress(currentTip, highAddress)
                }
            } else {
                val currentHighFile = log.getFileAddress(currentTip.highAddress)

                if (delta.files.first() != currentHighFile) {
                    // current file has been deleted, just pad it with nulls to respect log constraints
                    log.padWithNulls()
                }

                writeFiles(log, currentTip, delta, fileFactory)

                return {
                    log.endWrite()
                }
            }
        } catch (t: Throwable) {
            log.revertWrite(currentTip) // rollback potential padding and created files

            throw ExodusException.toExodusException(t, "Failed to replicate log")
        }
    }

    private fun checkPreconditions(log: @NotNull Log, currentTip: LogTip, delta: LogReplicationDelta) {
        if (delta.startAddress != currentTip.highAddress || delta.fileLengthBound != log.fileLengthBound) {
            throw IllegalArgumentException("Non-matching replication delta")
        }
    }

    private fun writeFiles(
            log: Log,
            currentTip: LogTip,
            delta: LogReplicationDelta,
            fileFactory: FileFactory
    ) {
        val fileSize = log.fileLengthBound

        val lastFile = log.getFileAddress(delta.highAddress)

        var lastFileWrite: WriteResult? = null

        var prevAddress = log.getFileAddress(currentTip.highAddress)

        var allWritten = 0L

        for (file in delta.files) {
            if (file < prevAddress) {
                throw IllegalStateException("Incorrect file order")
            }
            val startingLength = if (file == prevAddress) {
                currentTip.highAddress - prevAddress
            } else {
                0L
            }
            prevAddress = file

            val atLastFile = file == lastFile
            val expectedLength = if (atLastFile) {
                delta.highAddress - file
            } else {
                fileSize
            }

            val useLastPage = atLastFile && expectedLength != fileSize
            log.ensureWriter().setHighAddress(file + startingLength) // jump to the file
            val created = fileFactory.fetchFile(log, file, startingLength, expectedLength, useLastPage)

            if (created.written != (expectedLength - startingLength)) {
                throw IllegalStateException("Fetched unexpected bytes")
            }

            if (created.written == 0L) {
                return
            }

            allWritten += created.written

            if (useLastPage && (delta.highAddress - log.getHighPageAddress(delta.highAddress) != created.lastPageLength.toLong())) {
                throw IllegalStateException("Fetched unexpected last page bytes")
            }

            if (atLastFile) {
                lastFileWrite = created
            }
        }
        if (lastFileWrite == null) {
            throw IllegalArgumentException("Last file is not provided")
        }
        logger.debug { "Appended $allWritten bytes to log at ${log.location}" }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy