Skip to content
Permalink
Browse files

fix(OntologyResponderV2): Add a global ontology cache lock (#1637)

  • Loading branch information
benjamingeer committed Apr 27, 2020
1 parent 4f88f4f commit 18538650043940618e17f2f9213cebe52b9bb3fd
@@ -60,7 +60,7 @@ object IriLocker {
/**
* The number of times to try to acquire a lock before giving up.
*/
private val MAX_LOCK_RETRIES = 40
private val MAX_LOCK_RETRIES = 150

/**
* The total number of milliseconds that an API request should wait before giving up on acquiring a lock.
@@ -61,16 +61,17 @@ import scala.concurrent.Future
*/
class OntologyResponderV2(responderData: ResponderData) extends Responder(responderData) {

/**
* The name of the ontology cache.
*/
// The name of the ontology cache.
private val OntologyCacheName = "ontologyCache"

/**
* The cache key under which cached ontology data is stored.
*/
// The cache key under which cached ontology data is stored.
private val OntologyCacheKey = "ontologyCacheData"

// The global ontology cache lock. This is needed because every ontology update replaces the whole ontology cache
// (because definitions in one ontology can refer to definitions in another ontology). Without a global lock,
// concurrent updates (even to different ontologies) could overwrite each other.
private val ONTOLOGY_CACHE_LOCK_IRI = "http://rdfh.ch/ontologies"

/**
* The in-memory cache of ontologies.
*
@@ -97,7 +98,7 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
case StandoffEntityInfoGetRequestV2(standoffClassIris, standoffPropertyIris, requestingUser) => getStandoffEntityInfoResponseV2(standoffClassIris, standoffPropertyIris, requestingUser)
case StandoffClassesWithDataTypeGetRequestV2(requestingUser) => getStandoffStandoffClassesWithDataTypeV2(requestingUser)
case StandoffAllPropertyEntitiesGetRequestV2(requestingUser) => getAllStandoffPropertyEntitiesV2(requestingUser)
case CheckSubClassRequestV2(subClassIri, superClassIri, requestingUser) => checkSubClassV2(subClassIri, superClassIri, requestingUser)
case CheckSubClassRequestV2(subClassIri, superClassIri, requestingUser) => checkSubClassV2(subClassIri, superClassIri)
case SubClassesGetRequestV2(resourceClassIri, requestingUser) => getSubClassesV2(resourceClassIri, requestingUser)
case OntologyKnoraEntityIrisGetRequestV2(namedGraphIri, requestingUser) => getKnoraEntityIrisInNamedGraphV2(namedGraphIri, requestingUser)
case OntologyEntitiesGetRequestV2(ontologyIri, allLanguages, requestingUser) => getOntologyEntitiesV2(ontologyIri, allLanguages, requestingUser)
@@ -1365,7 +1366,7 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
* @param superClassIri the IRI of the resource or value class to check for (whether it is a a super class of `subClassIri` or not).
* @return a [[CheckSubClassResponseV2]].
*/
private def checkSubClassV2(subClassIri: SmartIri, superClassIri: SmartIri, requestingUser: UserADM): Future[CheckSubClassResponseV2] = {
private def checkSubClassV2(subClassIri: SmartIri, superClassIri: SmartIri): Future[CheckSubClassResponseV2] = {
for {
cacheData <- getCacheData
response = CheckSubClassResponseV2(
@@ -1771,10 +1772,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
// Make the internal ontology IRI.
internalOntologyIri = stringFormatter.makeProjectSpecificInternalOntologyIri(validOntologyName, createOntologyRequest.isShared, projectInfo.project.shortcode)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = createOntologyRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(internalOntologyIri)
)
} yield taskResult
@@ -1848,10 +1849,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
_ <- checkExternalOntologyIriForUpdate(changeOntologyMetadataRequest.ontologyIri)
internalOntologyIri = changeOntologyMetadataRequest.ontologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = changeOntologyMetadataRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(internalOntologyIri = internalOntologyIri)
)
} yield taskResult
@@ -2027,10 +2028,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalClassIri = externalClassIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = createClassRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalClassIri = internalClassIri,
internalOntologyIri = internalOntologyIri
@@ -2387,10 +2388,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalClassIri = externalClassIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = addCardinalitiesRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalClassIri = internalClassIri,
internalOntologyIri = internalOntologyIri
@@ -2552,10 +2553,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalClassIri = externalClassIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = changeCardinalitiesRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalClassIri = internalClassIri,
internalOntologyIri = internalOntologyIri
@@ -2653,10 +2654,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalClassIri = externalClassIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = deleteClassRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalClassIri = internalClassIri,
internalOntologyIri = internalOntologyIri
@@ -2771,10 +2772,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalPropertyIri = externalPropertyIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = deletePropertyRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalPropertyIri = internalPropertyIri,
internalOntologyIri = internalOntologyIri
@@ -2872,10 +2873,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
_ <- checkExternalOntologyIriForUpdate(deleteOntologyRequest.ontologyIri)
internalOntologyIri = deleteOntologyRequest.ontologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = deleteOntologyRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalOntologyIri = internalOntologyIri
)
@@ -3147,10 +3148,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalPropertyIri = externalPropertyIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = createPropertyRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalPropertyIri = internalPropertyIri,
internalOntologyIri = internalOntologyIri
@@ -3308,10 +3309,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalPropertyIri = externalPropertyIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = changePropertyLabelsOrCommentsRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalPropertyIri = internalPropertyIri,
internalOntologyIri = internalOntologyIri
@@ -3430,10 +3431,10 @@ class OntologyResponderV2(responderData: ResponderData) extends Responder(respon
internalClassIri = externalClassIri.toOntologySchema(InternalSchema)
internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema)

// Do the remaining pre-update checks and the update while holding an update lock on the ontology.
// Do the remaining pre-update checks and the update while holding a global ontology cache lock.
taskResult <- IriLocker.runWithIriLock(
apiRequestID = changeClassLabelsOrCommentsRequest.apiRequestID,
iri = internalOntologyIri.toString,
iri = ONTOLOGY_CACHE_LOCK_IRI,
task = () => makeTaskFuture(
internalClassIri = internalClassIri,
internalOntologyIri = internalOntologyIri
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory
*/
object CacheUtil {

val log = Logger(LoggerFactory.getLogger("org.knora.webapi.util.cache"))
val log: Logger = Logger(LoggerFactory.getLogger("org.knora.webapi.util.cache"))

/**
* Represents the configuration of a Knora application cache.
@@ -57,7 +57,7 @@ object CacheUtil {
* `overflowToDisk`, `eternal`, `timeToLiveSeconds`, and `timeToIdleSeconds`,
* representing configuration options for Ehcache caches.
*/
def createCaches(cacheConfigs: Seq[KnoraCacheConfig]) = {
def createCaches(cacheConfigs: Seq[KnoraCacheConfig]): Unit = {
val cacheManager = CacheManager.getInstance()
cacheConfigs.foreach {
cacheConfig =>
@@ -76,7 +76,7 @@ object CacheUtil {
/**
* Removes all caches.
*/
def removeAllCaches() = {
def removeAllCaches(): Unit = {
val cacheManager = CacheManager.getInstance()
cacheManager.removeAllCaches()
// println("CacheUtil: Removed all application caches")
@@ -21,7 +21,7 @@ class IriLockerSpec extends WordSpec with Matchers {
"IriLocker" should {
"not allow a request to acquire a lock when another request already has it" in {
def runLongTask(): Future[String] = Future {
Thread.sleep(4500)
Thread.sleep(16000)
SUCCESS
}

@@ -49,7 +49,7 @@ class IriLockerSpec extends WordSpec with Matchers {
)

val secondTaskFailedWithLockTimeout = try {
Await.result(secondTaskResultFuture, 5.seconds)
Await.result(secondTaskResultFuture, 20.seconds)
false
} catch {
case ale: ApplicationLockException => true

0 comments on commit 1853865

Please sign in to comment.