1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-24 14:50:46 +01:00

Backup: explicitely specify move options (#960)

* Backup: explicitely specify move options

We now specify that we want to atomically overwrite the existing backup file with the new one (fixes
a potential issue on Windows).
We also publish a specific notification when the backup process has been completed.
This commit is contained in:
Fabrice Drouin 2019-04-23 17:11:53 +02:00 committed by GitHub
parent 764a1ae84c
commit 064ba7df91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 21 additions and 8 deletions

View file

@ -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 = "/path/to/script.sh"
watcher-type = "bitcoind" // other *experimental* values include "electrum"
@ -149,4 +149,4 @@ eclair {
executor = "thread-pool-executor"
type = PinnedDispatcher
}
}
}

View file

@ -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}
@ -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
@ -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)

View file

@ -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") {
@ -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()