Skip to content

Commit 86f966b

Browse files
authored
Guard against failing agglomerate requests (#8767)
### URL of deployed dev instance (used for testing): - https://___.webknossos.xyz ### Steps to test: - create an annotation for a ds with an agglomerate file - enable a hdf5 mapping - switch to proofreading tool - every second request should fail (see network tab) - the user shouldn't notice this (except for the delay) - set `window.alwaysFail = true` <-- every request should fail <-- the mapping saga should not crash ### Issues: - contributes to #8715 ------ (Please delete unneeded items, merge only when none are left open) - [x] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [ ] Added migration guide entry if applicable (edit the same file as for the changelog) - [ ] Updated [documentation](../blob/master/docs) if applicable - [ ] Adapted [wk-libs python client](https://github.com/scalableminds/webknossos-libs/tree/master/webknossos/webknossos/client) if relevant API parts change - [ ] Removed dev-only changes like prints and application.conf edits - [ ] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [ ] Needs datastore update after deployment
1 parent 92835c9 commit 86f966b

File tree

6 files changed

+121
-66
lines changed

6 files changed

+121
-66
lines changed

frontend/javascripts/admin/rest_api.ts

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,15 +2001,17 @@ export async function getAgglomeratesForSegmentsFromDatastore<T extends number |
20012001
const segmentIdBuffer = serializeProtoListOfLong<T>(segmentIds);
20022002
const listArrayBuffer: ArrayBuffer = await doWithToken((token) => {
20032003
const params = new URLSearchParams({ token });
2004-
return Request.receiveArraybuffer(
2005-
`${dataStoreUrl}/data/datasets/${dataSourceId.owningOrganization}/${dataSourceId.directoryName}/layers/${layerName}/agglomerates/${mappingId}/agglomeratesForSegments?${params}`,
2006-
{
2007-
method: "POST",
2008-
body: segmentIdBuffer,
2009-
headers: {
2010-
"Content-Type": "application/octet-stream",
2004+
return Utils.retryAsyncFunction(() =>
2005+
Request.receiveArraybuffer(
2006+
`${dataStoreUrl}/data/datasets/${dataSourceId.owningOrganization}/${dataSourceId.directoryName}/layers/${layerName}/agglomerates/${mappingId}/agglomeratesForSegments?${params}`,
2007+
{
2008+
method: "POST",
2009+
body: segmentIdBuffer,
2010+
headers: {
2011+
"Content-Type": "application/octet-stream",
2012+
},
20112013
},
2012-
},
2014+
),
20132015
);
20142016
});
20152017
// Ensure that the values are bigint if the keys are bigint
@@ -2041,15 +2043,18 @@ export async function getAgglomeratesForSegmentsFromTracingstore<T extends numbe
20412043
);
20422044
const listArrayBuffer: ArrayBuffer = await doWithToken((token) => {
20432045
params.set("token", token);
2044-
return Request.receiveArraybuffer(
2045-
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomeratesForSegments?${params}`,
2046-
{
2047-
method: "POST",
2048-
body: segmentIdBuffer,
2049-
headers: {
2050-
"Content-Type": "application/octet-stream",
2046+
return Utils.retryAsyncFunction(() =>
2047+
Request.receiveArraybuffer(
2048+
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomeratesForSegments?${params}`,
2049+
{
2050+
method: "POST",
2051+
body: segmentIdBuffer,
2052+
headers: {
2053+
"Content-Type": "application/octet-stream",
2054+
},
2055+
showErrorToast: false,
20512056
},
2052-
},
2057+
),
20532058
);
20542059
});
20552060

@@ -2231,17 +2236,19 @@ export async function getEdgesForAgglomerateMinCut(
22312236
},
22322237
): Promise<Array<MinCutTargetEdge>> {
22332238
return doWithToken((token) =>
2234-
Request.sendJSONReceiveJSON(
2235-
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomerateGraphMinCut?token=${token}`,
2236-
{
2237-
data: {
2238-
...segmentsInfo,
2239-
// TODO: Proper 64 bit support (#6921)
2240-
segmentId1: Number(segmentsInfo.segmentId1),
2241-
segmentId2: Number(segmentsInfo.segmentId2),
2242-
agglomerateId: Number(segmentsInfo.agglomerateId),
2239+
Utils.retryAsyncFunction(() =>
2240+
Request.sendJSONReceiveJSON(
2241+
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomerateGraphMinCut?token=${token}`,
2242+
{
2243+
data: {
2244+
...segmentsInfo,
2245+
// TODO: Proper 64 bit support (#6921)
2246+
segmentId1: Number(segmentsInfo.segmentId1),
2247+
segmentId2: Number(segmentsInfo.segmentId2),
2248+
agglomerateId: Number(segmentsInfo.agglomerateId),
2249+
},
22432250
},
2244-
},
2251+
),
22452252
),
22462253
);
22472254
}
@@ -2262,16 +2269,18 @@ export async function getNeighborsForAgglomerateNode(
22622269
},
22632270
): Promise<NeighborInfo> {
22642271
return doWithToken((token) =>
2265-
Request.sendJSONReceiveJSON(
2266-
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomerateGraphNeighbors?token=${token}`,
2267-
{
2268-
data: {
2269-
...segmentInfo,
2270-
// TODO: Proper 64 bit support (#6921)
2271-
segmentId: Number(segmentInfo.segmentId),
2272-
agglomerateId: Number(segmentInfo.agglomerateId),
2272+
Utils.retryAsyncFunction(() =>
2273+
Request.sendJSONReceiveJSON(
2274+
`${tracingStoreUrl}/tracings/mapping/${tracingId}/agglomerateGraphNeighbors?token=${token}`,
2275+
{
2276+
data: {
2277+
...segmentInfo,
2278+
// TODO: Proper 64 bit support (#6921)
2279+
segmentId: Number(segmentInfo.segmentId),
2280+
agglomerateId: Number(segmentInfo.agglomerateId),
2281+
},
22732282
},
2274-
},
2283+
),
22752284
),
22762285
);
22772286
}

frontend/javascripts/components/error_boundary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export default class ErrorBoundary extends React.Component<
5252
<a href="#" onClick={this.clearLocalStorageAndReload}>
5353
here
5454
</a>{" "}
55-
to do so.
55+
to do so. If this does not help, please clear the browser cache.
5656
</Typography.Paragraph>
5757
<Alert
5858
style={{ maxHeight: "70vh", overflow: "auto" }}

frontend/javascripts/libs/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,3 +1391,24 @@ export const ColoredLogger = {
13911391
console.log(chalk.bgBlue(str), ...args);
13921392
},
13931393
};
1394+
1395+
export async function retryAsyncFunction<T>(
1396+
fn: () => Promise<T>,
1397+
retryCount: number = 3, // 0 retries would only do 1 attempt
1398+
exponentialDelayFactor: number = 1000,
1399+
): Promise<T> {
1400+
let currentAttempt = 0;
1401+
while (true) {
1402+
try {
1403+
return await fn();
1404+
} catch (exception) {
1405+
if (currentAttempt === retryCount) {
1406+
throw exception;
1407+
} else {
1408+
console.warn("Failed to run async function due to", exception, ". Will retry now.");
1409+
}
1410+
currentAttempt++;
1411+
await sleep(exponentialDelayFactor * 2 ** currentAttempt);
1412+
}
1413+
}
1414+
}

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

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -477,24 +477,33 @@ export function* updateLocalHdf5Mapping(
477477
intersection: mutableRemainingEntries,
478478
} = fastDiffSetAndMap(segmentIds as Set<NumberLike>, previousMapping);
479479

480-
const newEntries =
481-
editableMapping != null
482-
? yield* call(
483-
getAgglomeratesForSegmentsFromTracingstore,
484-
annotation.tracingStore.url,
485-
editableMapping.tracingId,
486-
Array.from(newSegmentIds),
487-
annotation.annotationId,
488-
annotation.version,
489-
)
490-
: yield* call(
491-
getAgglomeratesForSegmentsFromDatastore,
492-
dataset.dataStore.url,
493-
dataset,
494-
mappingLayerName,
495-
mappingName,
496-
Array.from(newSegmentIds),
497-
);
480+
let newEntries;
481+
try {
482+
newEntries =
483+
editableMapping != null
484+
? yield* call(
485+
getAgglomeratesForSegmentsFromTracingstore,
486+
annotation.tracingStore.url,
487+
editableMapping.tracingId,
488+
Array.from(newSegmentIds),
489+
annotation.annotationId,
490+
annotation.version,
491+
)
492+
: yield* call(
493+
getAgglomeratesForSegmentsFromDatastore,
494+
dataset.dataStore.url,
495+
dataset,
496+
mappingLayerName,
497+
mappingName,
498+
Array.from(newSegmentIds),
499+
);
500+
} catch (exception) {
501+
console.error("Could not load agglomerate ids for segments due to", exception);
502+
Toast.error(
503+
"Could not load agglomerate ids for segments. Some segments will remain hidden for now.",
504+
);
505+
return;
506+
}
498507

499508
// It is safe to mutate mutableRemainingEntries to compute the merged,
500509
// new mapping. See the definition of mutableRemainingEntries.

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

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -533,12 +533,19 @@ function* performMinCut(
533533
editableMappingId: volumeTracingId,
534534
};
535535

536-
const edgesToRemove = yield* call(
537-
getEdgesForAgglomerateMinCut,
538-
tracingStoreUrl,
539-
volumeTracingId,
540-
segmentsInfo,
541-
);
536+
let edgesToRemove;
537+
try {
538+
edgesToRemove = yield* call(
539+
getEdgesForAgglomerateMinCut,
540+
tracingStoreUrl,
541+
volumeTracingId,
542+
segmentsInfo,
543+
);
544+
} catch (exception) {
545+
console.error(exception);
546+
Toast.error("Could not determine which edges to delete for cut. Please try again.");
547+
return true;
548+
}
542549

543550
// Use untransformedPosition below because agglomerate trees should not have
544551
// any transforms, anyway.
@@ -598,12 +605,19 @@ function* performCutFromNeighbors(
598605
editableMappingId: volumeTracingId,
599606
};
600607

601-
const neighborInfo = yield* call(
602-
getNeighborsForAgglomerateNode,
603-
tracingStoreUrl,
604-
volumeTracingId,
605-
segmentsInfo,
606-
);
608+
let neighborInfo;
609+
try {
610+
neighborInfo = yield* call(
611+
getNeighborsForAgglomerateNode,
612+
tracingStoreUrl,
613+
volumeTracingId,
614+
segmentsInfo,
615+
);
616+
} catch (exception) {
617+
console.error(exception);
618+
Toast.error("Could not load neighbors of agglomerate node. Please try again.");
619+
return { didCancel: true };
620+
}
607621

608622
const edgesToRemove: Array<
609623
| {

unreleased_changes/8767.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- Fixed that WEBKNOSSOS would crash directly if the communication with the server was interrupted while using the proofreading tool.

0 commit comments

Comments
 (0)