Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backup: explicitely specify move options #960

Merged
merged 2 commits into from
Apr 23, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion eclair-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ eclair {
}

// override this with a script/exe that will be called everytime a new database backup has been created
backup-notify-script = ""
# backup-notify-script = ""
sstone marked this conversation as resolved.
Show resolved Hide resolved

watcher-type = "bitcoind" // other *experimental* values include "electrum"

Expand Down
16 changes: 14 additions & 2 deletions eclair-core/src/main/scala/fr/acinq/eclair/db/BackupHandler.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fr.acinq.eclair.db

import java.io.File
import java.nio.file.{Files, StandardCopyOption}

import akka.actor.{Actor, ActorLogging, Props}
import akka.dispatch.{BoundedMessageQueueSemantics, RequiresMessageQueue}
Expand Down Expand Up @@ -39,10 +40,16 @@ class BackupHandler private(databases: Databases, backupFile: File, backupScript
val start = System.currentTimeMillis()
val tmpFile = new File(backupFile.getAbsolutePath.concat(".tmp"))
databases.backup(tmpFile)
val result = tmpFile.renameTo(backupFile)
require(result, s"cannot rename $tmpFile to $backupFile")
// this will throw an exception if it fails, which is possible if the backup file is not on the same filesystem
// as the temporary file
Files.move(tmpFile.toPath, backupFile.toPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
val end = System.currentTimeMillis()

// publish a notification that we have updated our backup
context.system.eventStream.publish(BackupCompleted)

log.info(s"database backup triggered by channelId=${persisted.channelId} took ${end - start}ms")

backupScript_opt.foreach(backupScript => {
Try {
// run the script in the current thread and wait until it terminates
Expand All @@ -55,6 +62,11 @@ class BackupHandler private(databases: Databases, backupFile: File, backupScript
}
}

sealed trait BackupEvent

// this notification is sent when we have completed our backup process (our backup file is ready to be used)
case object BackupCompleted extends BackupEvent

object BackupHandler {
// using this method is the only way to create a BackupHandler actor
// we make sure that it uses a custom bounded mailbox, and a custom pinned dispatcher (i.e our actor will have its own thread pool with 1 single thread)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import java.sql.DriverManager
import java.util.UUID

import akka.actor.{ActorSystem, Props}
import akka.testkit.TestKit
import akka.testkit.{TestKit, TestProbe}
import fr.acinq.eclair.channel.ChannelPersisted
import fr.acinq.eclair.db.sqlite.SqliteChannelsDb
import fr.acinq.eclair.{TestConstants, TestUtils, randomBytes32}
import org.scalatest.FunSuiteLike

import scala.concurrent.duration._

class BackupHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {

test("process backups") {
Expand All @@ -26,10 +24,13 @@ class BackupHandlerSpec extends TestKit(ActorSystem("test")) with FunSuiteLike {
assert(db.channels.listLocalChannels() == Seq(channel))

val handler = system.actorOf(BackupHandler.props(db, dest, None))
val probe = TestProbe()
system.eventStream.subscribe(probe.ref, classOf[BackupEvent])

handler ! ChannelPersisted(null, TestConstants.Alice.nodeParams.nodeId, randomBytes32, null)
handler ! ChannelPersisted(null, TestConstants.Alice.nodeParams.nodeId, randomBytes32, null)
handler ! ChannelPersisted(null, TestConstants.Alice.nodeParams.nodeId, randomBytes32, null)
awaitCond(dest.exists(), 5 seconds)
probe.expectMsg(BackupCompleted)

val db1 = new SqliteChannelsDb(DriverManager.getConnection(s"jdbc:sqlite:$dest"))
val check = db1.listLocalChannels()
Expand Down