diff --git a/webapi/src/main/scala/org/knora/webapi/responders/IriLocker.scala b/webapi/src/main/scala/org/knora/webapi/responders/IriLocker.scala index 7fdfde3a62..25cd52634b 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/IriLocker.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/IriLocker.scala @@ -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. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index 9bc6c40433..6a3bf6e4c0 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -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 diff --git a/webapi/src/main/scala/org/knora/webapi/util/CacheUtil.scala b/webapi/src/main/scala/org/knora/webapi/util/CacheUtil.scala index c3450739f2..e92fee43bd 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/CacheUtil.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/CacheUtil.scala @@ -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") diff --git a/webapi/src/test/scala/org/knora/webapi/responders/IriLockerSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/IriLockerSpec.scala index 7397a7cb37..1b94ce243b 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/IriLockerSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/IriLockerSpec.scala @@ -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