Skip to content

Commit 527f1db

Browse files
committed
make mutex-acquisition ad-hoc when trying to save
1 parent 33de8ec commit 527f1db

File tree

5 files changed

+125
-31
lines changed

5 files changed

+125
-31
lines changed

frontend/javascripts/viewer/api/wk_dev.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type ApiLoader from "./api_loader";
1111
// Can be accessed via window.webknossos.DEV.flags. Only use this
1212
// for debugging or one off scripts.
1313
export const WkDevFlags = {
14-
logActions: false,
14+
logActions: true,
1515
sam: {
1616
useLocalMask: true,
1717
},
@@ -28,7 +28,7 @@ export const WkDevFlags = {
2828
disableLayerNameSanitization: false,
2929
},
3030
debugging: {
31-
showCurrentVersionInInfoTab: false,
31+
showCurrentVersionInInfoTab: true,
3232
},
3333
meshing: {
3434
marchingCubeSizeInTargetMag: [64, 64, 64] as Vector3,

frontend/javascripts/viewer/model/actions/save_actions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type DisableSavingAction = ReturnType<typeof disableSavingAction>;
2626
export type EnsureTracingsWereDiffedToSaveQueueAction = ReturnType<
2727
typeof ensureTracingsWereDiffedToSaveQueueAction
2828
>;
29+
export type EnsureMaySaveNowAction = ReturnType<typeof ensureMaySaveNowAction>;
30+
export type DoneSavingAction = ReturnType<typeof doneSavingAction>;
2931

3032
export type SaveAction =
3133
| PushSaveQueueTransaction
@@ -38,7 +40,9 @@ export type SaveAction =
3840
| UndoAction
3941
| RedoAction
4042
| DisableSavingAction
41-
| EnsureTracingsWereDiffedToSaveQueueAction;
43+
| EnsureTracingsWereDiffedToSaveQueueAction
44+
| EnsureMaySaveNowAction
45+
| DoneSavingAction;
4246

4347
// The action creators pushSaveQueueTransaction and pushSaveQueueTransactionIsolated
4448
// are typed so that update actions that need isolation are isolated in a group each.
@@ -132,3 +136,21 @@ export const ensureTracingsWereDiffedToSaveQueueAction = (callback: (tracingId:
132136
type: "ENSURE_TRACINGS_WERE_DIFFED_TO_SAVE_QUEUE",
133137
callback,
134138
}) as const;
139+
140+
export const ensureMaySaveNowAction = (callback: () => void) =>
141+
({
142+
type: "ENSURE_MAY_SAVE_NOW",
143+
callback,
144+
}) as const;
145+
146+
export const dispatchEnsureMaySaveNowAsync = async (dispatch: Dispatch<any>): Promise<void> => {
147+
const readyDeferred = new Deferred();
148+
const action = ensureMaySaveNowAction(() => readyDeferred.resolve(null));
149+
dispatch(action);
150+
await readyDeferred.promise();
151+
};
152+
153+
export const doneSavingAction = () =>
154+
({
155+
type: "DONE_SAVING",
156+
}) as const;

frontend/javascripts/viewer/model/sagas/saving/save_mutex_saga.tsx

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@ import { Button } from "antd";
33
import Toast from "libs/toast";
44
import messages from "messages";
55
import React from "react";
6-
import { call, cancel, cancelled, delay, fork, put, retry, takeEvery } from "typed-redux-saga";
6+
import {
7+
call,
8+
cancel,
9+
cancelled,
10+
delay,
11+
type FixedTask,
12+
fork,
13+
put,
14+
race,
15+
retry,
16+
take,
17+
takeEvery,
18+
} from "typed-redux-saga";
719
import {
820
type SetIsMutexAcquiredAction,
921
type SetOthersMayEditForAnnotationAction,
@@ -14,11 +26,15 @@ import {
1426
import type { Saga } from "viewer/model/sagas/effect-generators";
1527
import { select } from "viewer/model/sagas/effect-generators";
1628
import { ensureWkReady } from "../ready_sagas";
29+
import { EnsureMaySaveNowAction } from "viewer/model/actions/save_actions";
30+
31+
// Also refer to application.conf where annotation.mutex.expiryTime is defined
32+
// (typically, 2 minutes).
1733

1834
const MUTEX_NOT_ACQUIRED_KEY = "MutexCouldNotBeAcquired";
1935
const MUTEX_ACQUIRED_KEY = "AnnotationMutexAcquired";
20-
const RETRY_COUNT = 12;
2136
const ACQUIRE_MUTEX_INTERVAL = 1000 * 60;
37+
const RETRY_COUNT = 12; // 12 retries with 60/12=5 seconds backup delay
2238

2339
// todop
2440
const DISABLE_EAGER_MUTEX_ACQUISITION = true;
@@ -32,27 +48,23 @@ function* getDoesHaveMutex(): Saga<boolean> {
3248
}
3349

3450
export function* acquireAnnotationMutexMaybe(): Saga<void> {
35-
if (DISABLE_EAGER_MUTEX_ACQUISITION) {
36-
return;
37-
}
51+
yield* call(ensureWkReady);
3852
const initialAllowUpdate = yield* select(
3953
(state) => state.annotation.restrictions.initialAllowUpdate,
4054
);
4155
if (!initialAllowUpdate) {
42-
// We are in an read-only annotation.
56+
// We are in an read-only annotation. There's no point in acquiring mutexes.
57+
console.log("exit mutex saga");
4358
return;
4459
}
4560
const mutexLogicState: MutexLogicState = {
4661
isInitialRequest: true,
4762
};
4863

49-
yield* call(ensureWkReady);
5064
yield* fork(watchMutexStateChangesForNotification, mutexLogicState);
5165

52-
let runningTryAcquireMutexContinuouslySaga = yield* fork(
53-
tryAcquireMutexContinuously,
54-
mutexLogicState,
55-
);
66+
let runningTryAcquireMutexContinuouslySaga: FixedTask<void> | null;
67+
5668
function* reactToOthersMayEditChanges({
5769
othersMayEdit,
5870
}: SetOthersMayEditForAnnotationAction): Saga<void> {
@@ -65,8 +77,9 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
6577
mutexLogicState,
6678
);
6779
} else {
68-
// othersMayEdit was turned off. The user editing it should be able to edit the annotation.
69-
// let's check that owner === activeUser, anyway.
80+
// othersMayEdit was turned off by the activeUser. Since this is only
81+
// allowed by the owner, they should be able to edit the annotation, too.
82+
// Still, let's check that owner === activeUser to be extra safe.
7083
const owner = yield* select((storeState) => storeState.annotation.owner);
7184
const activeUser = yield* select((state) => state.activeUser);
7285
if (activeUser && owner?.id === activeUser?.id) {
@@ -75,16 +88,55 @@ export function* acquireAnnotationMutexMaybe(): Saga<void> {
7588
}
7689
}
7790
yield* takeEvery("SET_OTHERS_MAY_EDIT_FOR_ANNOTATION", reactToOthersMayEditChanges);
91+
92+
if (DISABLE_EAGER_MUTEX_ACQUISITION) {
93+
console.log("listening to all ENSURE_MAY_SAVE_NOW");
94+
yield* takeEvery("ENSURE_MAY_SAVE_NOW", resolveEnsureMaySaveNowActions);
95+
while (true) {
96+
console.log("taking ENSURE_MAY_SAVE_NOW");
97+
yield* take("ENSURE_MAY_SAVE_NOW");
98+
console.log("took ENSURE_MAY_SAVE_NOW");
99+
const { doneSaving } = yield race({
100+
tryAcquireMutexContinuously: fork(tryAcquireMutexContinuously, mutexLogicState),
101+
doneSaving: take("DONE_SAVING"),
102+
});
103+
if (doneSaving) {
104+
yield call(releaseMutex);
105+
}
106+
}
107+
} else {
108+
runningTryAcquireMutexContinuouslySaga = yield* fork(
109+
tryAcquireMutexContinuously,
110+
mutexLogicState,
111+
);
112+
}
78113
}
79114

80-
function* tryAcquireMutexContinuously(mutexLogicState: MutexLogicState): Saga<void> {
115+
function* resolveEnsureMaySaveNowActions(action: EnsureMaySaveNowAction) {
116+
/*
117+
* For each EnsureMaySaveNowAction wait until, we have the mutex. Then call
118+
* the callback.
119+
*/
120+
while (true) {
121+
const doesHaveMutex = yield* select(getDoesHaveMutex);
122+
if (doesHaveMutex) {
123+
action.callback();
124+
return;
125+
}
126+
yield* take("SET_BLOCKED_BY_USER");
127+
}
128+
}
129+
130+
function* tryAcquireMutexContinuously(mutexLogicState: MutexLogicState): Saga<never> {
131+
console.log("started tryAcquireMutexContinuously");
81132
const annotationId = yield* select((storeState) => storeState.annotation.annotationId);
82133
const activeUser = yield* select((state) => state.activeUser);
83134
mutexLogicState.isInitialRequest = true;
84135

85136
// We can simply use an infinite loop here, because the saga will be cancelled by
86137
// reactToOthersMayEditChanges when othersMayEdit is set to false.
87138
while (true) {
139+
console.log("tryAcquireMutexContinuously loop");
88140
const blockedByUser = yield* select((state) => state.annotation.blockedByUser);
89141
if (blockedByUser == null || blockedByUser.id !== activeUser?.id) {
90142
// If the annotation is currently not blocked by the active user,
@@ -114,7 +166,9 @@ function* tryAcquireMutexContinuously(mutexLogicState: MutexLogicState): Saga<vo
114166
// Therefore, we also print the error in the test context.
115167
console.error("Error while trying to acquire mutex:", error);
116168
}
169+
// todop: I think this needs to happen in a finally block?
117170
const wasCanceled = yield* cancelled();
171+
console.log("wasCanceled", wasCanceled);
118172
if (!wasCanceled) {
119173
console.error("Error while trying to acquire mutex.", error);
120174
yield* put(setBlockedByUserAction(undefined));
@@ -159,3 +213,15 @@ function* watchMutexStateChangesForNotification(mutexLogicState: MutexLogicState
159213
},
160214
);
161215
}
216+
217+
function* releaseMutex() {
218+
const annotationId = yield* select((storeState) => storeState.annotation.annotationId);
219+
yield* retry(
220+
RETRY_COUNT,
221+
ACQUIRE_MUTEX_INTERVAL / RETRY_COUNT,
222+
acquireAnnotationMutex,
223+
annotationId,
224+
);
225+
yield* put(setAnnotationAllowUpdateAction(true));
226+
yield* put(setBlockedByUserAction(null));
227+
}

frontend/javascripts/viewer/model/sagas/saving/save_queue_draining.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { call, delay, put, race, take } from "typed-redux-saga";
1414
import { ControlModeEnum } from "viewer/constants";
1515
import { getMagInfo } from "viewer/model/accessors/dataset_accessor";
1616
import {
17+
dispatchEnsureMaySaveNowAsync,
18+
doneSavingAction,
1719
setLastSaveTimestampAction,
1820
setSaveBusyAction,
1921
setVersionNumberAction,
@@ -30,11 +32,9 @@ import {
3032
PUSH_THROTTLE_TIME,
3133
SAVE_RETRY_WAITING_TIME,
3234
} from "viewer/model/sagas/saving/save_saga_constants";
33-
import { Model } from "viewer/singletons";
35+
import { Model, Store } from "viewer/singletons";
3436
import type { SaveQueueEntry } from "viewer/store";
3537

36-
const ONE_YEAR_MS = 365 * 24 * 3600 * 1000;
37-
3838
export function* pushSaveQueueAsync(): Saga<never> {
3939
yield* call(ensureWkReady);
4040

@@ -65,6 +65,9 @@ export function* pushSaveQueueAsync(): Saga<never> {
6565
});
6666
yield* put(setSaveBusyAction(true));
6767

68+
// Wait until we may save
69+
yield* call(dispatchEnsureMaySaveNowAsync, Store.dispatch);
70+
6871
// Send (parts of) the save queue to the server.
6972
// There are two main cases:
7073
// 1) forcePush is true
@@ -96,6 +99,7 @@ export function* pushSaveQueueAsync(): Saga<never> {
9699
break;
97100
}
98101
}
102+
yield* put(doneSavingAction());
99103
yield* put(setSaveBusyAction(false));
100104
}
101105
}

frontend/javascripts/viewer/model/sagas/volume/proofread_saga.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import {
22
type NeighborInfo,
3-
acquireAnnotationMutex,
43
getAgglomeratesForSegmentsFromTracingstore,
54
getEdgesForAgglomerateMinCut,
65
getNeighborsForAgglomerateNode,
76
getPositionForSegmentInAgglomerate,
8-
releaseAnnotationMutex,
97
} from "admin/rest_api";
108
import { V3 } from "libs/mjs";
119
import Toast from "libs/toast";
@@ -444,7 +442,8 @@ function* handleSkeletonProofreadingAction(action: Action): Saga<void> {
444442

445443
yield* put(pushSaveQueueTransaction(items));
446444
yield* call([Model, Model.ensureSavedState]);
447-
yield* call(releaseAnnotationMutex, annotationId);
445+
// todop
446+
// yield* call(releaseAnnotationMutex, annotationId);
448447

449448
if (action.type === "MIN_CUT_AGGLOMERATE_WITH_NODE_IDS" || action.type === "DELETE_EDGE") {
450449
if (sourceAgglomerateId !== targetAgglomerateId) {
@@ -794,7 +793,8 @@ function* handleProofreadMergeOrMinCut(action: Action) {
794793

795794
yield* put(pushSaveQueueTransaction(items));
796795
yield* call([Model, Model.ensureSavedState]);
797-
yield* call(releaseAnnotationMutex, annotationId);
796+
// todop
797+
// yield* call(releaseAnnotationMutex, annotationId);
798798

799799
if (action.type === "MIN_CUT_AGGLOMERATE") {
800800
console.log("start updating the mapping after a min-cut");
@@ -954,7 +954,8 @@ function* handleProofreadCutFromNeighbors(action: Action) {
954954

955955
yield* put(pushSaveQueueTransaction(items));
956956
yield* call([Model, Model.ensureSavedState]);
957-
yield* call(releaseAnnotationMutex, annotationId);
957+
// todop
958+
// yield* call(releaseAnnotationMutex, annotationId);
958959

959960
// Now that the changes are saved, we can split the mapping locally (because it requires
960961
// communication with the back-end).
@@ -1138,12 +1139,13 @@ function* prepareSplitOrMerge(isSkeletonProofreading: boolean): Saga<Preparation
11381139
return null;
11391140
}
11401141

1141-
const annotationId = yield* select((state) => state.annotation.annotationId);
1142-
const { canEdit } = yield* call(acquireAnnotationMutex, annotationId);
1143-
if (!canEdit) {
1144-
Toast.error("Could not acquire mutex. Somebody else is proofreading at the moment.");
1145-
return null;
1146-
}
1142+
// todop
1143+
// const annotationId = yield* select((state) => state.annotation.annotationId);
1144+
// const { canEdit } = yield* call(acquireAnnotationMutex, annotationId);
1145+
// if (!canEdit) {
1146+
// Toast.error("Could not acquire mutex. Somebody else is proofreading at the moment.");
1147+
// return null;
1148+
// }
11471149

11481150
return {
11491151
agglomerateFileMag,

0 commit comments

Comments
 (0)