Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotation Locking Mechanism #6819

Merged
merged 28 commits into from
Feb 23, 2023
Merged

Annotation Locking Mechanism #6819

merged 28 commits into from
Feb 23, 2023

Conversation

fm3
Copy link
Member

@fm3 fm3 commented Feb 6, 2023

new route POST /api/annotations/<annotationId>/acquireMutex (no body) will, for othersMayEdit annotations, return json body with two fields: canEdit: bool, blockedByUser: user (optional, only set if blocked. Uses compact version of user json, just like in user/owner in annotation json)

URL of deployed dev instance (used for testing):

  • https://___.webknossos.xyz

Steps to test:

  • create an additional user account / promote sample user2 to also be an admin.
  • create an annotation with sample user 1, make it editable by others keep it open.
  • Open the annotation in a private tab with sample user2. There it should switch to read only and show a toast that sample user is currently editing the annotation
  • When you close the annotation with sample user and wait for max 3 minutes, the annotation should be available to sample user2 and automatically reload the annotation once the mutex is acquired.
  • Now reopen the annotation with sample user and try to turn of othersMayEdit. This should not be possible, as sample user2 currently has the mutex.
  • close the annotation with sample user2
  • wait 3 mins and sample user should now be able to edit the annotation and the othersMayEdit setting again.#
  • and of cause: Play around with it the way you want to discover uncovered edge cases :D

Issues:


@fm3
Copy link
Member Author

fm3 commented Feb 6, 2023

@MichaelBuessemeyer I think the route is already usable, should be possible to start developing the front-end part :)

TODO backend:

  • clean up expired mutexes
  • extract mutex timeout to config, agree on good default value
  • consider moving the mutexes from main memory to postgres to avoid losing them on wk restart
  • include time logging to figure out in production if we need to tackle concurrent performance

@fm3 fm3 changed the title Annotation Locking Mechanism WIP: Annotation Locking Mechanism Feb 9, 2023
@fm3 fm3 mentioned this pull request Feb 13, 2023
39 tasks
@@ -59,6 +59,7 @@ webKnossos {
cache {
user.timeout = 3 minutes
}
annotation.mutex.expiryTime = 2 minutes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fm3 As this is a setting that might be changed, I think it would be a good idea to expose it to the frontend. Thus the frontend can adjust to the timeout duration so it requests the mutex in time.

If that setting is not designed to be changed, ignore my post 📰

@MichaelBuessemeyer
Copy link
Contributor

@fm3 I don't know whether that's needed :)

[ ] Needs datastore update after deployment

@MichaelBuessemeyer MichaelBuessemeyer changed the title WIP: Annotation Locking Mechanism Annotation Locking Mechanism Feb 17, 2023
@MichaelBuessemeyer MichaelBuessemeyer marked this pull request as ready for review February 17, 2023 12:09
Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff 👍 The code looks very good. I only left some nitpicks/edge cases.

Kudos for writing the tests 💯 Didn't review those yet, though. I'll test after the feedback is addressed :)

CHANGELOG.unreleased.md Outdated Show resolved Hide resolved
frontend/javascripts/messages.tsx Outdated Show resolved Hide resolved
frontend/javascripts/messages.tsx Outdated Show resolved Hide resolved
frontend/javascripts/oxalis/model/sagas/annotation_saga.ts Outdated Show resolved Hide resolved
frontend/javascripts/oxalis/model/sagas/annotation_saga.ts Outdated Show resolved Hide resolved
frontend/javascripts/oxalis/model/sagas/annotation_saga.ts Outdated Show resolved Hide resolved
frontend/javascripts/oxalis/model/sagas/annotation_saga.ts Outdated Show resolved Hide resolved
frontend/javascripts/oxalis/model/sagas/annotation_saga.ts Outdated Show resolved Hide resolved
Comment on lines 236 to 241
if (isInitialRequest && canEdit) {
// Only set allow update to true in case the first try to get the mutex succeeded.
yield* put(setAnnotationAllowUpdateAction(true));
} else {
yield* put(setAnnotationAllowUpdateAction(false));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (isInitialRequest && canEdit) {
// Only set allow update to true in case the first try to get the mutex succeeded.
yield* put(setAnnotationAllowUpdateAction(true));
} else {
yield* put(setAnnotationAllowUpdateAction(false));
}
// Only set allow update to true in case the first try to get the mutex succeeded.
yield* put(setAnnotationAllowUpdateAction(isInitialRequest && canEdit));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while my suggestion should be equal to the current code, I just noticed during testing that the logic itself is faulty. at the moment, on each iteration that is not the first, the boolean will be false and thus disabling the "is allowed" property. thus, I cannot edit my own annotation even when I got the mutex.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the moment, on each iteration that is not the first, the boolean will be false and thus disabling the "is allowed" property. thus, I cannot edit my own annotation even when I got the mutex.

Ah, I see what the problem is. Sorry, that I missed that. I did not test manually for long enough for this to happen 😅 .

The code now remembers whether the mutex could be acquired from the beginning. Now the code only sets the "is allowed" to false, if the mutex could wasn't acquired from the beginning or canEdit sent by the server is false. This should be the correct logic right?

The new logic should now be:
=> Keep allowUpdate true in case the mutex was acquired initially and the server sends back canEdit = true
=> once the server sends canEdit = false allowUpdate will never be set to true again until the user reloads the page.
=> If the mutex wasn't acquired initially, the allowUpdate will always be false until the user reloads the page.

This should cover all cases now, right? And blocking all updates from the annotation once the mutex was lost only a single time should be correct, right? Because there could be another potential user that had the mutex in the meantime and made some small changes to the annotation.

@philippotto
Copy link
Member

another thing I just noticed: If I'm the owner of an annotation, but don't have the mutex currently, I cannot disable the "may others edit" property (because the annotation is read-only for me). one could argue that this is fine, because the owner shouldn't disturb another user that is currently working on it, but I feel like this can be annoying if a user decides that they want to regain full control.

@fm3 @MichaelBuessemeyer do you have thoughts on that (regardless of implementation)?
implementation-wise it might only be a front-end thing (not sure, though).

Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my one comment. I think, the logic is a bit broken now so that the mutex cannot be kept. I wonder why the tests didn't catch that?

Co-authored-by: Philipp Otto <philippotto@users.noreply.github.com>
 - 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
@MichaelBuessemeyer
Copy link
Contributor

see my one comment. I think, the logic is a bit broken now so the mutex cannot be kept. I wonder why the tests didn't catch that?

That was due to the tests not testing the case where the mutex was acquired correctly multiple times in a row. They only tested for one successful request and then another failing one (with canEdit = false). I added a test catching this case now.

Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works excellent now 👍 Only left one renaming suggestion..

UX-wise one thing caught my attention: If I'm viewing my own shared annotation, but don't have the mutex, the navbar switches to the read-only mode which is fine per se. However, (apart from the toast which can be closed), the UI doesn't tell me anything about this state. The "Copy to my account" even suggests that this annotation isn't mine (even though it is). Do you think the navbar could be tweaked a bit in that state? I think, you could check for annotation.owner == activeUser && isReadOnly. In that case, the copy-button could simply say "Duplicate" and an antd tag could be next to the avatar saying "Locked by ..." (with a tooltip which explains a bit more).

return;
}
const othersMayEdit = yield* select((state) => state.tracing.othersMayEdit);
const ONE_MINUTE = 1000 * 60;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather give this variable a semantic meaning (e.g., acquireMutexInterval).

@MichaelBuessemeyer
Copy link
Contributor

@philippotto Thanks for the suggestions. Using a tag as a component doesn't seem very visually appealing to me, as it does not fit into the style of the navbar. But I cannot think of another component that could be used here instead :/

I additionally added a tooltip to the tag that uses the same message as the toast.

Visually it looks like this now:
image
I did not know where exactly to put it. Here is the other variation.
image
I personally prefer the second one.
I could also put the tag as the right-most item, but I think that the avatar should always be the right-most item. To I left out this possibility.

Another thing I noticed is that these messages frequently pop up:
image
Should I add the possibility to disable them or something like that? I think they are quite annoying. And I'd guess if another person is actively editing the annotation, this notification will nearly always pop up and annoy the user that is just waiting or viewing the annotation.

And this is an example image of the renaming of the "copy to my account" button:
image

I'll fix the tests now 😅

@philippotto
Copy link
Member

Awesome!

@philippotto Thanks for the suggestions. Using a tag as a component doesn't seem very visually appealing to me, as it does not fit into the style of the navbar. But I cannot think of another component that could be used here instead :/

Visually it looks like this now: image I did not know where exactly to put it. Here is the other variation. image I personally prefer the second one.

I prefer the second version, too, and think it looks alright 👍

Another thing I noticed is that these messages frequently pop up: image Should I add the possibility to disable them or something like that? I think they are quite annoying. And I'd guess if another person is actively editing the annotation, this notification will nearly always pop up and annoy the user that is just waiting or viewing the annotation.

Hm, yeah, you're probably right. Maybe adapt the saga to not display the warnings if the "others may edit" setting is true (in that case one has either locked the annotation --> no problem. OR: someone else has locked it --> navbar already shows what's up).

And this is an example image of the renaming of the "copy to my account" button: image

👍

@MichaelBuessemeyer
Copy link
Contributor

And this is how it looks, when the user acquired the mutex and only needs to reload:
image

Copy link
Member

@philippotto philippotto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, lets ship it :)

@MichaelBuessemeyer MichaelBuessemeyer merged commit a6075d3 into master Feb 23, 2023
@MichaelBuessemeyer MichaelBuessemeyer deleted the annotation-mutex branch February 23, 2023 07:18
Comment on lines +771 to +772
trailingNavItems.push(
<AnnotationLockedByUserTag blockedByUser={blockedByUser} activeUser={activeUser} />,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I forgot to add a unique key attribute here 🙈

hotzenklotz added a commit that referenced this pull request Mar 3, 2023
…d-v4.24

* 'master' of github.com:scalableminds/webknossos:
  Implement http range requests for HttpsSeekableByteChannel (#6869)
  new GH action for adding issues to project board
  Fix links in Changelog (#6881)
  adds dedicated explore method for zarr datasets with a datasource-properties.json (#6879)
  Release 23.03.0 (#6880)
  Fix superUser being wrongly marked as organization owners (#6876)
  Followups for OME-TIFF export (#6874)
  Fix reload-precomputed-mesh functionality (#6875)
  Adds OME-TIFF export (#6838)
  Add evolutions 99,100 to migration guide (#6871)
  Add link to imprint and privacy to help menu (#6870)
  Annotation Locking Mechanism (#6819)
hotzenklotz added a commit that referenced this pull request Mar 6, 2023
…come_header_UI

* 'master' of github.com:scalableminds/webknossos: (34 commits)
  Slim down view mode dropdown by using icons (#6900)
  Logging on password reset/change (#6901)
  When merging volume tracings, also merge segment lists (#6882)
  avoid spinner when switching tabs in dashboard (#6894)
  Upgrade Antd to v4.24 (#6865)
  Support n5 end-chunks with chunksize differing from metadata chunksize (#6890)
  Implement http range requests for HttpsSeekableByteChannel (#6869)
  new GH action for adding issues to project board
  Fix links in Changelog (#6881)
  adds dedicated explore method for zarr datasets with a datasource-properties.json (#6879)
  Release 23.03.0 (#6880)
  Fix superUser being wrongly marked as organization owners (#6876)
  Followups for OME-TIFF export (#6874)
  Fix reload-precomputed-mesh functionality (#6875)
  Adds OME-TIFF export (#6838)
  Add evolutions 99,100 to migration guide (#6871)
  Add link to imprint and privacy to help menu (#6870)
  Annotation Locking Mechanism (#6819)
  Update deprecated antd <Menu> (#6860)
  Add functions to get and set segment colors to the frontend API (#6853)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Automatic Annotation Locking Mechanism (for collaboration)
4 participants