-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Annotation Locking Mechanism (#6819)
* add route to test-and-acquire annotation mutex * compact writes * move mutex storage to postgres * naming * POST * WIP: log time for annotation mutex request * move to request logging trait * lint * Adding frontent requesting mutex for annotations * WIP: add tests for frontend mutex acquire saga * finish writing tests for frontend mutex acquire saga * clean up * add feedback * add version assertions to sql evolutions * apply feedback and fix annotation_saga tests * Apply suggestions from code review Co-authored-by: Philipp Otto <[email protected]> * apply pr feedback - fix logic bug - always disable updating when the mutex acquiring results in an exception - add test covering case where the mutex can be acquired after some requests * add additional visual indications when annotation is locked * don't show annotation version mismatch while not having the annotations mutex --------- Co-authored-by: Michael Büßemeyer <[email protected]> Co-authored-by: MichaelBuessemeyer <[email protected]> Co-authored-by: Philipp Otto <[email protected]>
- Loading branch information
1 parent
fa47f73
commit a6075d3
Showing
30 changed files
with
826 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package models.annotation | ||
|
||
import akka.actor.ActorSystem | ||
import com.scalableminds.util.accesscontext.GlobalAccessContext | ||
import com.scalableminds.util.time.Instant | ||
import com.scalableminds.util.tools.Fox | ||
import com.scalableminds.webknossos.datastore.helpers.IntervalScheduler | ||
import com.scalableminds.webknossos.schema.Tables.AnnotationMutexesRow | ||
import com.typesafe.scalalogging.LazyLogging | ||
import models.user.{UserDAO, UserService} | ||
import net.liftweb.common.Full | ||
import play.api.inject.ApplicationLifecycle | ||
import play.api.libs.json.{JsObject, Json} | ||
import utils.{ObjectId, WkConf} | ||
import utils.sql.{SimpleSQLDAO, SqlClient} | ||
|
||
import javax.inject.Inject | ||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.duration.{DurationInt, FiniteDuration} | ||
|
||
case class AnnotationMutex(annotationId: ObjectId, userId: ObjectId, expiry: Instant) | ||
|
||
case class MutexResult(canEdit: Boolean, blockedByUser: Option[ObjectId]) | ||
|
||
class AnnotationMutexService @Inject()(val lifecycle: ApplicationLifecycle, | ||
val system: ActorSystem, | ||
wkConf: WkConf, | ||
userDAO: UserDAO, | ||
userService: UserService, | ||
annotationMutexDAO: AnnotationMutexDAO) | ||
extends IntervalScheduler | ||
with LazyLogging { | ||
|
||
override protected def tickerInterval: FiniteDuration = 1 hour | ||
|
||
override protected def tick(): Unit = { | ||
logger.info("Cleaning up expired annotation mutexes...") | ||
annotationMutexDAO.deleteExpired() | ||
() | ||
} | ||
|
||
private val defaultExpiryTime = wkConf.WebKnossos.Annotation.Mutex.expiryTime | ||
|
||
def tryAcquiringAnnotationMutex(annotationId: ObjectId, userId: ObjectId)( | ||
implicit ec: ExecutionContext): Fox[MutexResult] = | ||
this.synchronized { | ||
for { | ||
mutexBox <- annotationMutexDAO.findOne(annotationId).futureBox | ||
result <- mutexBox match { | ||
case Full(mutex) => | ||
if (mutex.userId == userId) | ||
refresh(mutex) | ||
else | ||
Fox.successful(MutexResult(canEdit = false, blockedByUser = Some(mutex.userId))) | ||
case _ => | ||
acquire(annotationId, userId) | ||
} | ||
} yield result | ||
} | ||
|
||
private def acquire(annotationId: ObjectId, userId: ObjectId): Fox[MutexResult] = | ||
for { | ||
_ <- annotationMutexDAO.upsertOne(AnnotationMutex(annotationId, userId, Instant.in(defaultExpiryTime))) | ||
} yield MutexResult(canEdit = true, None) | ||
|
||
private def refresh(mutex: AnnotationMutex): Fox[MutexResult] = | ||
for { | ||
_ <- annotationMutexDAO.upsertOne(mutex.copy(expiry = Instant.in(defaultExpiryTime))) | ||
} yield MutexResult(canEdit = true, None) | ||
|
||
def publicWrites(mutexResult: MutexResult)(implicit ec: ExecutionContext): Fox[JsObject] = | ||
for { | ||
userOpt <- Fox.runOptional(mutexResult.blockedByUser)(user => userDAO.findOne(user)(GlobalAccessContext)) | ||
userJsonOpt <- Fox.runOptional(userOpt)(user => userService.compactWrites(user)) | ||
} yield | ||
Json.obj( | ||
"canEdit" -> mutexResult.canEdit, | ||
"blockedByUser" -> userJsonOpt | ||
) | ||
|
||
} | ||
|
||
class AnnotationMutexDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionContext) | ||
extends SimpleSQLDAO(sqlClient) { | ||
|
||
private def parse(r: AnnotationMutexesRow): AnnotationMutex = | ||
AnnotationMutex( | ||
ObjectId(r._Annotation), | ||
ObjectId(r._User), | ||
Instant.fromSql(r.expiry) | ||
) | ||
|
||
def findOne(annotationId: ObjectId): Fox[AnnotationMutex] = | ||
for { | ||
rows <- run(q"""SELECT _annotation, _user, expiry | ||
FROM webknossos.annotation_mutexes | ||
WHERE _annotation = $annotationId | ||
AND expiry > NOW()""".as[AnnotationMutexesRow]) | ||
first <- rows.headOption | ||
parsed = parse(first) | ||
} yield parsed | ||
|
||
def upsertOne(annotationMutex: AnnotationMutex): Fox[Unit] = | ||
for { | ||
_ <- run(q"""INSERT INTO webknossos.annotation_mutexes(_annotation, _user, expiry) | ||
VALUES(${annotationMutex.annotationId}, ${annotationMutex.userId}, ${annotationMutex.expiry}) | ||
ON CONFLICT (_annotation) | ||
DO UPDATE SET | ||
_user = ${annotationMutex.userId}, | ||
expiry = ${annotationMutex.expiry} | ||
""".asUpdate) | ||
} yield () | ||
|
||
def deleteExpired(): Fox[Unit] = | ||
for { | ||
_ <- run(q"DELETE FROM webknossos.annotation_mutexes WHERE expiry < NOW()".asUpdate) | ||
} yield () | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
START TRANSACTION; | ||
|
||
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 99, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; | ||
|
||
CREATE TABLE webknossos.annotation_mutexes( | ||
_annotation CHAR(24) PRIMARY KEY, | ||
_user CHAR(24) NOT NULL, | ||
expiry TIMESTAMP NOT NULL | ||
); | ||
|
||
ALTER TABLE webknossos.annotation_mutexes | ||
ADD CONSTRAINT annotation_ref FOREIGN KEY(_annotation) REFERENCES webknossos.annotations(_id) ON DELETE CASCADE DEFERRABLE, | ||
ADD CONSTRAINT user_ref FOREIGN KEY(_user) REFERENCES webknossos.users(_id) ON DELETE CASCADE DEFERRABLE; | ||
|
||
UPDATE webknossos.releaseInformation | ||
SET schemaVersion = 100; | ||
|
||
COMMIT TRANSACTION; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
START TRANSACTION; | ||
|
||
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 100, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; | ||
|
||
DROP TABLE webknossos.annotation_mutexes; | ||
|
||
UPDATE webknossos.releaseInformation | ||
SET schemaVersion = 99; | ||
|
||
COMMIT TRANSACTION; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.