Fix for SQLite DB busy error on Azure (#1395)

depending on an environmental variable is used either 
default locking mode or a mode that uses flock syscall
This commit is contained in:
Łukasz Olczak 2021-01-19 10:07:17 +01:00 committed by GitHub
parent 3b48fc7e66
commit d257615ef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 119 additions and 33 deletions

View File

@ -0,0 +1,40 @@
package org.enso.languageserver.boot
/** Signal where the lang. server is deployed.
*/
sealed trait DeploymentType
object DeploymentType {
/** Desktop deployment.
*/
case object Desktop extends DeploymentType
/** Azure deployment.
*/
case object Azure extends DeploymentType
/** Determines the current deployment type from environment variables.
* @return the current deployment type
*/
def fromEnvironment(): DeploymentType = {
if (sys.env.contains(DeploymentTypeVariableName)) {
val value = sys.env(DeploymentTypeVariableName)
fromString(value)
} else {
Desktop
}
}
/** Determines a current deployment type from a string value.
* @return a deployment type
*/
def fromString(value: String): DeploymentType =
value.toLowerCase.trim match {
case "desktop" | "" => Desktop
case "azure" => Azure
}
private lazy val DeploymentTypeVariableName = "DEPLOYMENT_TYPE"
}

View File

@ -5,6 +5,7 @@ import java.net.URI
import akka.actor.ActorSystem
import org.enso.jsonrpc.JsonRpcServer
import org.enso.languageserver.boot.DeploymentType.{Azure, Desktop}
import org.enso.languageserver.capability.CapabilityRouter
import org.enso.languageserver.data._
import org.enso.languageserver.effect.ZioExec
@ -34,6 +35,7 @@ import org.enso.languageserver.util.binary.BinaryEncoder
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
import org.enso.polyglot.{LanguageInfo, RuntimeOptions, RuntimeServerInfo}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
import org.enso.searcher.sqlite.LockingMode
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Context
import org.graalvm.polyglot.io.MessageEndpoint
@ -84,7 +86,19 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
log.trace("Created ActorSystem")
val sqlDatabase =
SqlDatabase(languageServerConfig.directories.suggestionsDatabaseFile)
DeploymentType.fromEnvironment() match {
case Desktop =>
SqlDatabase(
languageServerConfig.directories.suggestionsDatabaseFile.toString
)
case Azure =>
SqlDatabase(
languageServerConfig.directories.suggestionsDatabaseFile.toString,
Some(LockingMode.UnixFlock)
)
}
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)(system.dispatcher)
val versionsRepo = new SqlVersionsRepo(sqlDatabase)(system.dispatcher)
log.trace("Created SQL Repos")

View File

@ -19,11 +19,11 @@ import org.enso.languageserver.session.JsonSession
import org.enso.languageserver.session.SessionRouter.DeliverToJsonController
import org.enso.polyglot.data.Tree
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.searcher.{FileVersionsRepo, SuggestionsRepo}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
import org.enso.searcher.{FileVersionsRepo, SuggestionsRepo}
import org.enso.testkit.RetrySpec
import org.enso.text.{ContentVersion, Sha3_224VersionCalculator}
import org.enso.text.editing.model.Position
import org.enso.text.{ContentVersion, Sha3_224VersionCalculator}
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
@ -594,10 +594,12 @@ class SuggestionsHandlerSpec
): Unit = {
val testContentRoot = Files.createTempDirectory(null).toRealPath()
sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.toFile))
val config = newConfig(testContentRoot.toFile)
val router = TestProbe("session-router")
val connector = TestProbe("runtime-connector")
val sqlDatabase = SqlDatabase(config.directories.suggestionsDatabaseFile)
val config = newConfig(testContentRoot.toFile)
val router = TestProbe("session-router")
val connector = TestProbe("runtime-connector")
val sqlDatabase = SqlDatabase(
config.directories.suggestionsDatabaseFile.toString
)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)
val versionsRepo = new SqlVersionsRepo(sqlDatabase)
val handler = newSuggestionsHandler(

View File

@ -81,13 +81,13 @@ class BaseServerTest extends JsonRpcServerTestKit {
InputRedirectionController.props(stdIn, stdInSink, sessionRouter)
)
val zioExec = ZioExec(zio.Runtime.default)
val sqlDatabase =
SqlDatabase(config.directories.suggestionsDatabaseFile.toString)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)(system.dispatcher)
val versionsRepo = new SqlVersionsRepo(sqlDatabase)(system.dispatcher)
override def clientControllerFactory: ClientControllerFactory = {
val zioExec = ZioExec(zio.Runtime.default)
val sqlDatabase = SqlDatabase(config.directories.suggestionsDatabaseFile)
val suggestionsRepo = new SqlSuggestionsRepo(sqlDatabase)(system.dispatcher)
val versionsRepo = new SqlVersionsRepo(sqlDatabase)(system.dispatcher)
val fileManager =
system.actorOf(FileManager.props(config, new FileSystem, zioExec))
val bufferRegistry =

View File

@ -4,6 +4,7 @@ searcher {
driver = "org.sqlite.JDBC"
connectionPool = disabled
properties.journal_mode = "wal"
properties.locking_mode = "EXCLUSIVE"
numThreads = 1
}
}

View File

@ -1,9 +1,8 @@
package org.enso.searcher.sql
import java.io.File
import com.typesafe.config.{Config, ConfigFactory}
import org.enso.searcher.Database
import org.enso.searcher.sqlite.LockingMode
import slick.dbio.DBIO
import slick.jdbc.SQLiteProfile
import slick.jdbc.SQLiteProfile.api._
@ -43,24 +42,31 @@ object SqlDatabase {
* @param filename the database file path
* @return new sql database instance
*/
def apply(filename: File): SqlDatabase =
apply(filename.toString)
/** Create [[SqlDatabase]] instance.
*
* @param filename the database file path
* @return new sql database instance
*/
def apply(filename: String): SqlDatabase = {
def apply(
filename: String,
maybeLockingMode: Option[LockingMode] = None
): SqlDatabase = {
val config = ConfigFactory
.parseString(s"""$configPath.url = "${jdbcUrl(filename)}"""")
.parseString(
s"""$configPath.url = "${jdbcUrl(filename, maybeLockingMode)}""""
)
.withFallback(ConfigFactory.load())
new SqlDatabase(Some(config))
}
/** Create JDBC URL from the file path. */
private def jdbcUrl(filename: String): String =
s"jdbc:sqlite:${escapePath(filename)}"
private def jdbcUrl(
filename: String,
maybeLockingMode: Option[LockingMode]
): String = {
maybeLockingMode match {
case None =>
s"jdbc:sqlite:${escapePath(filename)}"
case Some(lockingMode) =>
s"jdbc:sqlite:file:${escapePath(filename)}?vfs=${lockingMode.name}"
}
}
/** Escape Windows path. */
private def escapePath(path: String): String =

View File

@ -0,0 +1,24 @@
package org.enso.searcher.sqlite
/** A mode used to handle file locking in SQLite */
case class LockingMode private (name: String)
object LockingMode {
/** Default mode that uses POSIX advisory locks.
*/
val UnixPosix = LockingMode("unix")
/** It obtains and holds an exclusive lock on database files,
* preventing other processes from accessing the database.
* It uses the `flock` system call.
*/
val UnixFlock = LockingMode("unix-excl")
/** It uses dot-file locking rather than POSIX advisory locks. */
val UnixDotFile = LockingMode("unix-dotfile")
/** All file locking operations are no-ops. */
val UnixNone = LockingMode("unix-none")
}

View File

@ -11,9 +11,9 @@ ADD runtime.jar /opt/enso/runtime.jar
RUN chown -hR enso:enso /opt/enso
RUN chmod -R u=rX,g=rX /opt/enso
RUN mkdir -p /home/enso/workspace
RUN chown -hR enso:enso /home/enso/workspace
RUN chmod -R u=rwX,g=rwX /home/enso/workspace
RUN mkdir -p /volumes
RUN chown -hR enso:enso /volumes
RUN chmod -R u=rwX,g=rwX /volumes
USER enso:enso
@ -24,7 +24,6 @@ ENTRYPOINT ["java", "-jar", "-Dtruffle.class.path.append=runtime.jar", "-Dpolyg
EXPOSE 30001
EXPOSE 30002
VOLUME /home/enso/workspace
CMD ["--server", "--rpc-port", "30001", "--data-port", "30002", "--root-id", "00000000-0000-0000-0000-000000000001", "--path", "/home/enso/workspace", "--interface", "0.0.0.0"]
VOLUME /volumes/workspace
CMD ["--server", "--daemon", "--rpc-port", "30001", "--data-port", "30002", "--root-id", "00000000-0000-0000-0000-000000000001", "--path", "/volumes/workspace", "--interface", "0.0.0.0", "--log-level", "INFO"]