Skip to content

Commit 5444647

Browse files
authored
Fix stuck saving when more than ~1500 buckets were labeled at once (#8409)
* tmp: try to debug stuck saving * tmp: attempts to better guard against silent stack overflow * clean up * fix stuck saving * further clean up * clean up * update changelog * format * add console.error in case of ts error
1 parent c09afa9 commit 5444647

11 files changed

+30
-21
lines changed

CHANGELOG.unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
1818
### Fixed
1919
- Fixed a bug that would lock a non existing mapping to an empty segmentation layer under certain conditions. [#8401](https://github.com/scalableminds/webknossos/pull/8401)
2020
- Fixed the alignment of the button that allows restricting floodfill operations to a bounding box. [#8388](https://github.com/scalableminds/webknossos/pull/8388)
21+
- Fixed rare bug where saving got stuck. [#8409](https://github.com/scalableminds/webknossos/pull/8409)
2122

2223
### Removed
2324

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export type SaveAction =
3838

3939
// The action creators pushSaveQueueTransaction and pushSaveQueueTransactionIsolated
4040
// are typed so that update actions that need isolation are isolated in a group each.
41-
// From this point on, we can assume that the groups fulfil the isolation requirement.
41+
// From this point on, we can assume that the groups fulfill the isolation requirement.
4242
export const pushSaveQueueTransaction = (
4343
items: Array<UpdateActionWithoutIsolationRequirement>,
4444
): PushSaveQueueTransaction =>

frontend/javascripts/oxalis/model/reducers/save_reducer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ function SaveReducer(state: OxalisState, action: Action): OxalisState {
157157
}
158158

159159
case "SET_LAST_SAVE_TIMESTAMP": {
160-
return updateKey2(state, "save", "lastSaveTimestamp", action.timestamp);
160+
return updateKey(state, "save", { lastSaveTimestamp: action.timestamp });
161161
}
162162

163163
case "SET_VERSION_NUMBER": {

frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ function shouldDisplaySegmentationData(): boolean {
150150
return !onlyViewing3dViewport;
151151
}
152152

153-
export function* warnAboutSegmentationZoom(): Saga<void> {
153+
export function* warnAboutSegmentationZoom(): Saga<never> {
154154
function* warnMaybe(): Saga<void> {
155155
const segmentationLayer = Model.getVisibleSegmentationLayer();
156156

frontend/javascripts/oxalis/model/sagas/annotation_tool_saga.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { select } from "oxalis/model/sagas/effect-generators";
1313
import { call, put, take } from "typed-redux-saga";
1414
import { ensureWkReady } from "./ready_sagas";
1515

16-
export function* watchToolDeselection(): Saga<void> {
16+
export function* watchToolDeselection(): Saga<never> {
1717
yield* call(ensureWkReady);
1818
let previousTool = yield* select((state) => state.uiInformation.activeTool);
1919

@@ -38,7 +38,7 @@ export function* watchToolDeselection(): Saga<void> {
3838
}
3939
}
4040

41-
export function* watchToolReset(): Saga<void> {
41+
export function* watchToolReset(): Saga<never> {
4242
while (true) {
4343
yield* take("ESCAPE");
4444
const activeTool = yield* select((state) => state.uiInformation.activeTool);

frontend/javascripts/oxalis/model/sagas/mapping_saga.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ function createBucketRetrievalSourceChannel(layerName: string) {
225225
}, buffers.sliding<BucketRetrievalSource>(1));
226226
}
227227

228-
function* watchChangedBucketsForLayer(layerName: string): Saga<void> {
228+
function* watchChangedBucketsForLayer(layerName: string): Saga<never> {
229229
const dataCube = yield* call([Model, Model.getCubeByLayerName], layerName);
230230
const bucketChannel = yield* call(createBucketDataChangedChannel, dataCube);
231231

frontend/javascripts/oxalis/model/sagas/mesh_saga.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1199,7 +1199,7 @@ function* handleMeshVisibilityChange(action: UpdateMeshVisibilityAction): Saga<v
11991199
segmentMeshController.setMeshVisibility(id, visibility, layerName, additionalCoordinates);
12001200
}
12011201

1202-
export function* handleAdditionalCoordinateUpdate(): Saga<void> {
1202+
export function* handleAdditionalCoordinateUpdate(): Saga<never> {
12031203
// We want to prevent iterating through all additional coordinates to adjust the mesh visibility, so we store the
12041204
// previous additional coordinates in this method. Thus we have to catch SET_ADDITIONAL_COORDINATES actions in a
12051205
// while-true loop and register this saga in the root saga instead of calling from the mesh saga.
@@ -1212,11 +1212,13 @@ export function* handleAdditionalCoordinateUpdate(): Saga<void> {
12121212
const action = (yield* take(["SET_ADDITIONAL_COORDINATES"]) as any) as FlycamAction;
12131213
// Satisfy TS
12141214
if (action.type !== "SET_ADDITIONAL_COORDINATES") {
1215-
throw new Error("Unexpected action type");
1215+
// Don't throw as this would interfere with the never return type
1216+
console.error("Unexpected action.type. Ignoring SET_ADDITIONAL_COORDINATES action...");
1217+
continue;
12161218
}
12171219
const meshRecords = segmentMeshController.meshesGroupsPerSegmentId;
12181220

1219-
if (action.values == null || action.values.length === 0) break;
1221+
if (action.values == null || action.values.length === 0) continue;
12201222
const newAdditionalCoordKey = getAdditionalCoordinatesAsString(action.values);
12211223

12221224
for (const additionalCoordinates of [action.values, previousAdditionalCoordinates]) {

frontend/javascripts/oxalis/model/sagas/quick_select_ml_saga.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ function* getMask(
130130
];
131131
}
132132

133-
function* showApproximatelyProgress(amount: number, expectedDurationPerItemMs: number) {
133+
function* showApproximatelyProgress(
134+
amount: number,
135+
expectedDurationPerItemMs: number,
136+
): Saga<never> {
134137
// The progress bar is split into amount + 1 chunks. The first amount
135138
// chunks are filled after expectedDurationPerItemMs passed.
136139
// Afterwards, only one chunk is missing. With each additional iteration,

frontend/javascripts/oxalis/model/sagas/root_saga.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { setIsWkReadyAction } from "../actions/ui_actions";
2323
import { warnIfEmailIsUnverified } from "./user_saga";
2424

2525
let rootSagaCrashed = false;
26-
export default function* rootSaga(): Saga<void> {
26+
export default function* rootSaga(): Saga<never> {
2727
while (true) {
2828
rootSagaCrashed = false;
2929
const task = yield* fork(restartableSaga);

frontend/javascripts/oxalis/model/sagas/save_saga.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Toast from "libs/toast";
77
import { sleep } from "libs/utils";
88
import window, { alert, document, location } from "libs/window";
99
import _ from "lodash";
10+
import memoizeOne from "memoize-one";
1011
import messages from "messages";
1112
import { ControlModeEnum } from "oxalis/constants";
1213
import { getMagInfo } from "oxalis/model/accessors/dataset_accessor";
@@ -57,7 +58,7 @@ import { takeEveryWithBatchActionSupport } from "./saga_helpers";
5758

5859
const ONE_YEAR_MS = 365 * 24 * 3600 * 1000;
5960

60-
export function* pushSaveQueueAsync(): Saga<void> {
61+
export function* pushSaveQueueAsync(): Saga<never> {
6162
yield* call(ensureWkReady);
6263

6364
yield* put(setLastSaveTimestampAction());
@@ -87,7 +88,7 @@ export function* pushSaveQueueAsync(): Saga<void> {
8788
});
8889
yield* put(setSaveBusyAction(true));
8990

90-
// Send (parts) of the save queue to the server.
91+
// Send (parts of) the save queue to the server.
9192
// There are two main cases:
9293
// 1) forcePush is true
9394
// The user explicitly requested to save an annotation.
@@ -100,7 +101,7 @@ export function* pushSaveQueueAsync(): Saga<void> {
100101
// The auto-save interval was reached at time T. The following code
101102
// will determine how many items are in the save queue at this time T.
102103
// Exactly that many items will be sent to the server.
103-
// New items that might be added to the save queue during saving, will
104+
// New items that might be added to the save queue during saving, will be
104105
// ignored (they will be picked up in the next iteration of this loop).
105106
// Otherwise, the risk of a high number of save-requests (see case 1)
106107
// would be present here, too (note the risk would be greater, because the
@@ -118,7 +119,6 @@ export function* pushSaveQueueAsync(): Saga<void> {
118119
break;
119120
}
120121
}
121-
122122
yield* put(setSaveBusyAction(false));
123123
}
124124
}
@@ -183,7 +183,6 @@ export function* sendSaveRequestToServer(): Saga<number> {
183183
const startTime = Date.now();
184184
yield* call(
185185
sendRequestWithToken,
186-
187186
`${tracingStoreUrl}/tracings/annotation/${annotationId}/update?token=`,
188187
{
189188
method: "POST",
@@ -281,12 +280,16 @@ export function* sendSaveRequestToServer(): Saga<number> {
281280
}
282281

283282
function* markBucketsAsNotDirty(saveQueue: Array<SaveQueueEntry>) {
283+
const getLayerAndMagInfoForTracingId = memoizeOne((tracingId: string) => {
284+
const segmentationLayer = Model.getSegmentationTracingLayer(tracingId);
285+
const segmentationMagInfo = getMagInfo(segmentationLayer.mags);
286+
return [segmentationLayer, segmentationMagInfo] as const;
287+
});
284288
for (const saveEntry of saveQueue) {
285289
for (const updateAction of saveEntry.actions) {
286290
if (updateAction.name === "updateBucket") {
287291
const { actionTracingId: tracingId } = updateAction.value;
288-
const segmentationLayer = Model.getSegmentationTracingLayer(tracingId);
289-
const segmentationMagInfo = yield* call(getMagInfo, segmentationLayer.mags);
292+
const [segmentationLayer, segmentationMagInfo] = getLayerAndMagInfoForTracingId(tracingId);
290293

291294
const { position, mag, additionalCoordinates } = updateAction.value;
292295
const magIndex = segmentationMagInfo.getIndexByMag(mag);
@@ -377,7 +380,7 @@ export function* saveTracingAsync(): Saga<void> {
377380

378381
export function* setupSavingForTracingType(
379382
initializeAction: InitializeSkeletonTracingAction | InitializeVolumeTracingAction,
380-
): Saga<void> {
383+
): Saga<never> {
381384
/*
382385
Listen to changes to the annotation and derive UpdateActions from the
383386
old and new state.
@@ -450,7 +453,7 @@ const VERSION_POLL_INTERVAL_COLLAB = 10 * 1000;
450453
const VERSION_POLL_INTERVAL_READ_ONLY = 60 * 1000;
451454
const VERSION_POLL_INTERVAL_SINGLE_EDITOR = 30 * 1000;
452455

453-
function* watchForSaveConflicts() {
456+
function* watchForSaveConflicts(): Saga<never> {
454457
function* checkForNewVersion() {
455458
const allowSave = yield* select(
456459
(state) => state.tracing.restrictions.allowSave && state.tracing.restrictions.allowUpdate,

frontend/javascripts/oxalis/throttled_store.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Store.subscribe(() => {
1818
}
1919
});
2020

21-
async function go() {
21+
async function go(): Promise<never> {
2222
while (true) {
2323
await waitForUpdate.promise();
2424
waitForUpdate = new Deferred();

0 commit comments

Comments
 (0)