Skip to content

Commit 954401a

Browse files
authored
[fga] try any spicedb operation 3 times (#18704)
1 parent e5384ca commit 954401a

File tree

2 files changed

+52
-41
lines changed

2 files changed

+52
-41
lines changed

components/server/src/authorization/spicedb-authorizer.ts

Lines changed: 46 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,30 @@ import { SpiceDBClientProvider } from "./spicedb";
1414
import * as grpc from "@grpc/grpc-js";
1515
import { isFgaChecksEnabled } from "./authorizer";
1616

17+
async function tryThree<T>(errMessage: string, code: (attempt: number) => Promise<T>): Promise<T> {
18+
let attempt = 0;
19+
// we do sometimes see INTERNAL errors from SpiceDB, so we retry a few times
20+
// last time we checked it was 15 times per day (check logs)
21+
while (attempt++ < 3) {
22+
try {
23+
return await code(attempt);
24+
} catch (err) {
25+
if (err.code === grpc.status.INTERNAL && attempt < 3) {
26+
log.warn(errMessage, err, {
27+
attempt,
28+
});
29+
} else {
30+
log.error(errMessage, err, {
31+
attempt,
32+
});
33+
// we don't try again on other errors
34+
throw err;
35+
}
36+
}
37+
}
38+
throw new Error("unreachable");
39+
}
40+
1741
@injectable()
1842
export class SpiceDBAuthorizer {
1943
constructor(
@@ -35,11 +59,12 @@ export class SpiceDBAuthorizer {
3559
if (!featureEnabled) {
3660
return true;
3761
}
38-
3962
const timer = spicedbClientLatency.startTimer();
4063
let error: Error | undefined;
4164
try {
42-
const response = await this.client.checkPermission(req, this.callOptions);
65+
const response = await tryThree("[spicedb] Failed to perform authorization check.", () =>
66+
this.client.checkPermission(req, this.callOptions),
67+
);
4368
const permitted = response.permissionship === v1.CheckPermissionResponse_Permissionship.HAS_PERMISSION;
4469

4570
return permitted;
@@ -55,56 +80,38 @@ export class SpiceDBAuthorizer {
5580
}
5681

5782
async writeRelationships(...updates: v1.RelationshipUpdate[]): Promise<v1.WriteRelationshipsResponse | undefined> {
58-
let tries = 0;
59-
// we do sometimes see INTERNAL errors from SpiceDB, so we retry a few times
60-
// last time we checked it was 15 times per day (check logs)
61-
while (tries++ < 3) {
62-
const timer = spicedbClientLatency.startTimer();
63-
let error: Error | undefined;
64-
try {
65-
const response = await this.client.writeRelationships(
83+
const timer = spicedbClientLatency.startTimer();
84+
let error: Error | undefined;
85+
try {
86+
const response = await tryThree("[spicedb] Failed to write relationships.", () =>
87+
this.client.writeRelationships(
6688
v1.WriteRelationshipsRequest.create({
6789
updates,
6890
}),
6991
this.callOptions,
70-
);
71-
log.info("[spicedb] Successfully wrote relationships.", { response, updates, tries });
92+
),
93+
);
94+
log.info("[spicedb] Successfully wrote relationships.", { response, updates });
7295

73-
return response;
74-
} catch (err) {
75-
error = err;
76-
if (err.code === grpc.status.INTERNAL && tries < 3) {
77-
log.warn("[spicedb] Failed to write relationships.", err, {
78-
updates: new TrustedValue(updates),
79-
tries,
80-
});
81-
} else {
82-
log.error("[spicedb] Failed to write relationships.", err, {
83-
updates: new TrustedValue(updates),
84-
tries,
85-
});
86-
// we don't try again on other errors
87-
return;
88-
}
89-
} finally {
90-
observeSpicedbClientLatency("write", error, timer());
91-
}
96+
return response;
97+
} finally {
98+
observeSpicedbClientLatency("write", error, timer());
9299
}
93100
}
94101

95102
async deleteRelationships(req: v1.DeleteRelationshipsRequest): Promise<v1.ReadRelationshipsResponse[]> {
96103
const timer = spicedbClientLatency.startTimer();
97104
let error: Error | undefined;
98105
try {
99-
const existing = await this.client.readRelationships(
100-
v1.ReadRelationshipsRequest.create(req),
101-
this.callOptions,
106+
const existing = await tryThree("readRelationships before deleteRelationships failed.", () =>
107+
this.client.readRelationships(v1.ReadRelationshipsRequest.create(req), this.callOptions),
102108
);
103109
if (existing.length > 0) {
104-
const response = await this.client.deleteRelationships(req, this.callOptions);
105-
const after = await this.client.readRelationships(
106-
v1.ReadRelationshipsRequest.create(req),
107-
this.callOptions,
110+
const response = await tryThree("deleteRelationships failed.", () =>
111+
this.client.deleteRelationships(req, this.callOptions),
112+
);
113+
const after = await tryThree("readRelationships failed.", () =>
114+
this.client.readRelationships(v1.ReadRelationshipsRequest.create(req), this.callOptions),
108115
);
109116
if (after.length > 0) {
110117
log.error("[spicedb] Failed to delete relationships.", { existing, after, request: req });
@@ -128,7 +135,7 @@ export class SpiceDBAuthorizer {
128135
}
129136

130137
async readRelationships(req: v1.ReadRelationshipsRequest): Promise<v1.ReadRelationshipsResponse[]> {
131-
return this.client.readRelationships(req, this.callOptions);
138+
return tryThree("readRelationships failed.", () => this.client.readRelationships(req, this.callOptions));
132139
}
133140

134141
/**

components/server/src/workspace/workspace-starter.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -828,9 +828,13 @@ export class WorkspaceStarter {
828828
try {
829829
let ideTasks: TaskConfig[] = [];
830830
try {
831-
ideTasks = JSON.parse(ideConfig.tasks);
831+
if (ideConfig.tasks && ideConfig.tasks.trim() !== "") {
832+
ideTasks = JSON.parse(ideConfig.tasks);
833+
}
832834
} catch (e) {
833-
console.error("failed get tasks from ide config:", e);
835+
log.info("failed parse tasks from ide config:", e, {
836+
tasks: ideConfig.tasks,
837+
});
834838
}
835839

836840
const configuration: WorkspaceInstanceConfiguration = {

0 commit comments

Comments
 (0)