fix: retry grpc calls upon service unavailable response (#1011)

* fix: retry grpc calls upon service unavailable response

* fix: retry grpc calls for session verification
This commit is contained in:
Karol Sójko 2023-12-28 15:21:40 +01:00 committed by GitHub
parent 6dde9209af
commit 7c67a5a0f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 92 additions and 27 deletions

14
.pnp.cjs generated
View file

@ -2190,10 +2190,10 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["@grpc/grpc-js", [\ ["@grpc/grpc-js", [\
["npm:1.9.12", {\ ["npm:1.9.13", {\
"packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.12-cb97be6754-fe13b04844.zip/node_modules/@grpc/grpc-js/",\ "packageLocation": "./.yarn/cache/@grpc-grpc-js-npm-1.9.13-33f9b49e10-c52150053c.zip/node_modules/@grpc/grpc-js/",\
"packageDependencies": [\ "packageDependencies": [\
["@grpc/grpc-js", "npm:1.9.12"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@grpc/proto-loader", "npm:0.7.10"],\ ["@grpc/proto-loader", "npm:0.7.10"],\
["@types/node", "npm:20.2.5"]\ ["@types/node", "npm:20.2.5"]\
],\ ],\
@ -5532,7 +5532,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/api-gateway/",\ "packageLocation": "./packages/api-gateway/",\
"packageDependencies": [\ "packageDependencies": [\
["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\ ["@standardnotes/api-gateway", "workspace:packages/api-gateway"],\
["@grpc/grpc-js", "npm:1.9.12"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\
["@standardnotes/domain-events", "workspace:packages/domain-events"],\ ["@standardnotes/domain-events", "workspace:packages/domain-events"],\
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\ ["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
@ -5582,7 +5582,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-sqs", "npm:3.462.0"],\ ["@aws-sdk/client-sqs", "npm:3.462.0"],\
["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\ ["@cbor-extract/cbor-extract-linux-arm64", "npm:2.1.1"],\
["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\ ["@cbor-extract/cbor-extract-linux-x64", "npm:2.1.1"],\
["@grpc/grpc-js", "npm:1.9.12"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@simplewebauthn/server", "npm:8.1.1"],\ ["@simplewebauthn/server", "npm:8.1.1"],\
["@simplewebauthn/typescript-types", "npm:8.0.0"],\ ["@simplewebauthn/typescript-types", "npm:8.0.0"],\
["@standardnotes/api", "npm:1.26.26"],\ ["@standardnotes/api", "npm:1.26.26"],\
@ -5809,7 +5809,7 @@ const RAW_RUNTIME_STATE =
"packageLocation": "./packages/grpc/",\ "packageLocation": "./packages/grpc/",\
"packageDependencies": [\ "packageDependencies": [\
["@standardnotes/grpc", "workspace:packages/grpc"],\ ["@standardnotes/grpc", "workspace:packages/grpc"],\
["@grpc/grpc-js", "npm:1.9.12"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@types/google-protobuf", "npm:3.15.10"],\ ["@types/google-protobuf", "npm:3.15.10"],\
["google-protobuf", "npm:3.21.2"],\ ["google-protobuf", "npm:3.21.2"],\
["grpc-tools", "npm:1.12.4"],\ ["grpc-tools", "npm:1.12.4"],\
@ -6082,7 +6082,7 @@ const RAW_RUNTIME_STATE =
["@aws-sdk/client-s3", "npm:3.462.0"],\ ["@aws-sdk/client-s3", "npm:3.462.0"],\
["@aws-sdk/client-sns", "npm:3.462.0"],\ ["@aws-sdk/client-sns", "npm:3.462.0"],\
["@aws-sdk/client-sqs", "npm:3.462.0"],\ ["@aws-sdk/client-sqs", "npm:3.462.0"],\
["@grpc/grpc-js", "npm:1.9.12"],\ ["@grpc/grpc-js", "npm:1.9.13"],\
["@standardnotes/api", "npm:1.26.26"],\ ["@standardnotes/api", "npm:1.26.26"],\
["@standardnotes/common", "workspace:packages/common"],\ ["@standardnotes/common", "workspace:packages/common"],\
["@standardnotes/domain-core", "workspace:packages/domain-core"],\ ["@standardnotes/domain-core", "workspace:packages/domain-core"],\

View file

@ -31,7 +31,7 @@
"start": "yarn node dist/bin/server.js" "start": "yarn node dist/bin/server.js"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.12", "@grpc/grpc-js": "^1.9.13",
"@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-core": "workspace:^",
"@standardnotes/domain-events": "workspace:*", "@standardnotes/domain-events": "workspace:*",
"@standardnotes/domain-events-infra": "workspace:*", "@standardnotes/domain-events-infra": "workspace:*",

View file

@ -8,6 +8,7 @@ import * as grpc from '@grpc/grpc-js'
import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface' import { CrossServiceTokenCacheInterface } from '../Cache/CrossServiceTokenCacheInterface'
import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface' import { ServiceProxyInterface } from '../Proxy/ServiceProxyInterface'
import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy' import { GRPCSyncingServerServiceProxy } from './GRPCSyncingServerServiceProxy'
import { Status } from '@grpc/grpc-js/build/src/constants'
export class GRPCServiceProxy implements ServiceProxyInterface { export class GRPCServiceProxy implements ServiceProxyInterface {
constructor( constructor(
@ -27,11 +28,14 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy, private gRPCSyncingServerServiceProxy: GRPCSyncingServerServiceProxy,
) {} ) {}
async validateSession(headers: { async validateSession(
authorization: string headers: {
sharedVaultOwnerContext?: string authorization: string
}): Promise<{ status: number; data: unknown; headers: { contentType: string } }> { sharedVaultOwnerContext?: string
return new Promise((resolve, reject) => { },
retryAttempt?: number,
): Promise<{ status: number; data: unknown; headers: { contentType: string } }> {
const promise = new Promise((resolve, reject) => {
try { try {
const request = new AuthorizationHeader() const request = new AuthorizationHeader()
request.setBearerToken(headers.authorization) request.setBearerToken(headers.authorization)
@ -80,6 +84,32 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
return reject(error) return reject(error)
} }
}) })
try {
const result = await promise
if (retryAttempt) {
this.logger.info(`Request to Auth Server succeeded after ${retryAttempt} retries`)
}
return result as { status: number; data: unknown; headers: { contentType: string } }
} catch (error) {
const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE
const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.info(`Retrying request to Auth Server for the ${nextRetryAttempt} time`)
return this.validateSession(headers, nextRetryAttempt)
}
throw error
}
} }
async callSyncingServer( async callSyncingServer(
@ -92,6 +122,21 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115' payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') { if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
await this.callSyncingServerGRPC(request, response, payload)
return
}
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload)
}
private async callSyncingServerGRPC(
request: Request,
response: Response,
payload?: Record<string, unknown> | string,
retryAttempt?: number,
): Promise<void> {
try {
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload) const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
response.status(result.status).send({ response.status(result.status).send({
@ -107,10 +152,30 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
data: result.data, data: result.data,
}) })
return if (retryAttempt) {
} this.logger.info(`Request to Syncing Server succeeded after ${retryAttempt} retries`, {
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
}
} catch (error) {
const requestDidNotMakeIt =
'code' in (error as Record<string, unknown>) && (error as Record<string, unknown>).code === Status.UNAVAILABLE
await this.callServer(this.syncingServerJsUrl, request, response, endpoint, payload) const tooManyRetryAttempts = retryAttempt && retryAttempt > 2
if (!tooManyRetryAttempts && requestDidNotMakeIt) {
await this.timer.sleep(50)
const nextRetryAttempt = retryAttempt ? retryAttempt + 1 : 1
this.logger.info(`Retrying request to Syncing Server for the ${nextRetryAttempt} time`, {
userId: response.locals.user ? response.locals.user.uuid : undefined,
})
return this.callSyncingServerGRPC(request, response, payload, nextRetryAttempt)
}
throw error
}
} }
async callRevisionsServer( async callRevisionsServer(

View file

@ -43,7 +43,7 @@
"@aws-sdk/client-sqs": "^3.462.0", "@aws-sdk/client-sqs": "^3.462.0",
"@cbor-extract/cbor-extract-linux-arm64": "^2.1.1", "@cbor-extract/cbor-extract-linux-arm64": "^2.1.1",
"@cbor-extract/cbor-extract-linux-x64": "^2.1.1", "@cbor-extract/cbor-extract-linux-x64": "^2.1.1",
"@grpc/grpc-js": "^1.9.12", "@grpc/grpc-js": "^1.9.13",
"@simplewebauthn/server": "^8.1.1", "@simplewebauthn/server": "^8.1.1",
"@simplewebauthn/typescript-types": "^8.0.0", "@simplewebauthn/typescript-types": "^8.0.0",
"@standardnotes/api": "^1.26.26", "@standardnotes/api": "^1.26.26",

View file

@ -27,7 +27,7 @@
"build": "tsc --build" "build": "tsc --build"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.12", "@grpc/grpc-js": "^1.9.13",
"google-protobuf": "^3.21.2" "google-protobuf": "^3.21.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -35,7 +35,7 @@
"@aws-sdk/client-s3": "^3.462.0", "@aws-sdk/client-s3": "^3.462.0",
"@aws-sdk/client-sns": "^3.462.0", "@aws-sdk/client-sns": "^3.462.0",
"@aws-sdk/client-sqs": "^3.462.0", "@aws-sdk/client-sqs": "^3.462.0",
"@grpc/grpc-js": "^1.9.12", "@grpc/grpc-js": "^1.9.13",
"@standardnotes/api": "^1.26.26", "@standardnotes/api": "^1.26.26",
"@standardnotes/common": "workspace:*", "@standardnotes/common": "workspace:*",
"@standardnotes/domain-core": "workspace:^", "@standardnotes/domain-core": "workspace:^",

View file

@ -1583,13 +1583,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@grpc/grpc-js@npm:^1.9.12": "@grpc/grpc-js@npm:^1.9.13":
version: 1.9.12 version: 1.9.13
resolution: "@grpc/grpc-js@npm:1.9.12" resolution: "@grpc/grpc-js@npm:1.9.13"
dependencies: dependencies:
"@grpc/proto-loader": "npm:^0.7.8" "@grpc/proto-loader": "npm:^0.7.8"
"@types/node": "npm:>=12.12.47" "@types/node": "npm:>=12.12.47"
checksum: fe13b04844b525ad860521589e2d640bb8cfeea46e3cb8e4eab537e0a4fcb04a033083c25d5c3cd4e061a6471c933f6f12e81dcc626acdcf68435e6e4a833a06 checksum: c52150053ca3911bf9ec5012265aa754627aba9c60577ef07c594c5c22896e939ec0f656cc130a54a8651ea0ae23f385a4a48868fc71ff56dff54eeaec8b6912
languageName: node languageName: node
linkType: hard linkType: hard
@ -4084,7 +4084,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@standardnotes/api-gateway@workspace:packages/api-gateway" resolution: "@standardnotes/api-gateway@workspace:packages/api-gateway"
dependencies: dependencies:
"@grpc/grpc-js": "npm:^1.9.12" "@grpc/grpc-js": "npm:^1.9.13"
"@standardnotes/domain-core": "workspace:^" "@standardnotes/domain-core": "workspace:^"
"@standardnotes/domain-events": "workspace:*" "@standardnotes/domain-events": "workspace:*"
"@standardnotes/domain-events-infra": "workspace:*" "@standardnotes/domain-events-infra": "workspace:*"
@ -4147,7 +4147,7 @@ __metadata:
"@aws-sdk/client-sqs": "npm:^3.462.0" "@aws-sdk/client-sqs": "npm:^3.462.0"
"@cbor-extract/cbor-extract-linux-arm64": "npm:^2.1.1" "@cbor-extract/cbor-extract-linux-arm64": "npm:^2.1.1"
"@cbor-extract/cbor-extract-linux-x64": "npm:^2.1.1" "@cbor-extract/cbor-extract-linux-x64": "npm:^2.1.1"
"@grpc/grpc-js": "npm:^1.9.12" "@grpc/grpc-js": "npm:^1.9.13"
"@simplewebauthn/server": "npm:^8.1.1" "@simplewebauthn/server": "npm:^8.1.1"
"@simplewebauthn/typescript-types": "npm:^8.0.0" "@simplewebauthn/typescript-types": "npm:^8.0.0"
"@standardnotes/api": "npm:^1.26.26" "@standardnotes/api": "npm:^1.26.26"
@ -4361,7 +4361,7 @@ __metadata:
version: 0.0.0-use.local version: 0.0.0-use.local
resolution: "@standardnotes/grpc@workspace:packages/grpc" resolution: "@standardnotes/grpc@workspace:packages/grpc"
dependencies: dependencies:
"@grpc/grpc-js": "npm:^1.9.12" "@grpc/grpc-js": "npm:^1.9.13"
"@types/google-protobuf": "npm:^3" "@types/google-protobuf": "npm:^3"
google-protobuf: "npm:^3.21.2" google-protobuf: "npm:^3.21.2"
grpc-tools: "npm:^1.12.4" grpc-tools: "npm:^1.12.4"
@ -4618,7 +4618,7 @@ __metadata:
"@aws-sdk/client-s3": "npm:^3.462.0" "@aws-sdk/client-s3": "npm:^3.462.0"
"@aws-sdk/client-sns": "npm:^3.462.0" "@aws-sdk/client-sns": "npm:^3.462.0"
"@aws-sdk/client-sqs": "npm:^3.462.0" "@aws-sdk/client-sqs": "npm:^3.462.0"
"@grpc/grpc-js": "npm:^1.9.12" "@grpc/grpc-js": "npm:^1.9.13"
"@standardnotes/api": "npm:^1.26.26" "@standardnotes/api": "npm:^1.26.26"
"@standardnotes/common": "workspace:*" "@standardnotes/common": "workspace:*"
"@standardnotes/domain-core": "workspace:^" "@standardnotes/domain-core": "workspace:^"