-
Notifications
You must be signed in to change notification settings - Fork 335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
handle failed api inits #1970
handle failed api inits #1970
Changes from 4 commits
cf42702
9a4ff1d
81e7f65
ec36e19
ce76d2c
97a6102
34f5778
47e8f39
b96ae09
2a2ee52
42cab93
dde4087
808cc5a
c1ba3be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,13 +24,14 @@ export interface ConnectionPoolItem<T> { | |
failed: boolean; | ||
lastRequestTime: number; | ||
connected: boolean; | ||
initFailed: boolean; | ||
timeoutId?: NodeJS.Timeout; | ||
} | ||
|
||
const logger = getLogger('connection-pool-state'); | ||
|
||
export interface IConnectionPoolStateManager<T extends IApiConnectionSpecific<any, any, any>> { | ||
addToConnections(endpoint: string, index: number, primary: boolean): Promise<void>; | ||
addToConnections(endpoint: string, index: number, primary: boolean, initFailed: boolean): Promise<void>; | ||
getNextConnectedApiIndex(): Promise<number | undefined>; | ||
// Async to be compatible with workers | ||
getFieldValue<K extends keyof ConnectionPoolItem<T>>(apiIndex: number, field: K): Promise<ConnectionPoolItem<T>[K]>; | ||
|
@@ -53,17 +54,23 @@ export class ConnectionPoolStateManager<T extends IApiConnectionSpecific<any, an | |
private pool: Record<number, ConnectionPoolItem<T>> = {}; | ||
|
||
//eslint-disable-next-line @typescript-eslint/require-await | ||
async addToConnections(endpoint: string, index: number, primary: boolean): Promise<void> { | ||
async addToConnections(endpoint: string, index: number, primary: boolean, initFailed: boolean): Promise<void> { | ||
//avoid overwriting state if init failed in one of the workers | ||
if (this.pool[index] && this.pool[index].initFailed) { | ||
return; | ||
} | ||
|
||
const poolItem: ConnectionPoolItem<T> = { | ||
primary: primary, | ||
performanceScore: 100, | ||
failureCount: 0, | ||
endpoint: endpoint, | ||
backoffDelay: 0, | ||
rateLimited: false, | ||
failed: false, | ||
failed: initFailed, | ||
connected: true, | ||
lastRequestTime: 0, | ||
initFailed: initFailed, | ||
}; | ||
this.pool[index] = poolItem; | ||
|
||
|
@@ -79,9 +86,15 @@ export class ConnectionPoolStateManager<T extends IApiConnectionSpecific<any, an | |
return primaryIndex; | ||
} | ||
|
||
const indices = Object.keys(this.pool) | ||
const initedIndices = Object.keys(this.pool) | ||
.map(Number) | ||
.filter((index) => !this.pool[index].backoffDelay && this.pool[index].connected); | ||
.filter((index) => !this.pool[index].initFailed); | ||
|
||
if (initedIndices.length === 0) { | ||
throw new Error(`Initialization failed for all endpoints. Please add healthier endpoints.`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem like the right place to throw this error. This isn't an init function |
||
} | ||
|
||
const indices = initedIndices.filter((index) => !this.pool[index].backoffDelay && this.pool[index].connected); | ||
|
||
if (indices.length === 0) { | ||
// If all endpoints are suspended, try to find a rate-limited one | ||
|
@@ -124,7 +137,13 @@ export class ConnectionPoolStateManager<T extends IApiConnectionSpecific<any, an | |
private getPrimaryEndpointIndex(): number | undefined { | ||
return Object.keys(this.pool) | ||
.map(Number) | ||
.find((index) => this.pool[index].primary && !this.pool[index].backoffDelay && this.pool[index].connected); | ||
.find( | ||
(index) => | ||
this.pool[index].primary && | ||
!this.pool[index].backoffDelay && | ||
this.pool[index].connected && | ||
!this.pool[index].initFailed | ||
); | ||
} | ||
|
||
get numConnections(): number { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,66 +96,71 @@ export class ApiService | |
} | ||
|
||
for await (const [i, endpoint] of network.endpoint.entries()) { | ||
const connection = await ApiPromiseConnection.create( | ||
endpoint, | ||
this.fetchBlocksBatches, | ||
{ | ||
chainTypes, | ||
}, | ||
); | ||
|
||
const api = connection.unsafeApi; | ||
try { | ||
const connection = await ApiPromiseConnection.create( | ||
endpoint, | ||
this.fetchBlocksBatches, | ||
{ | ||
chainTypes, | ||
}, | ||
); | ||
|
||
this.eventEmitter.emit(IndexerEvent.ApiConnected, { | ||
value: 1, | ||
apiIndex: i, | ||
endpoint: endpoint, | ||
}); | ||
const api = connection.unsafeApi; | ||
|
||
api.on('connected', () => { | ||
this.eventEmitter.emit(IndexerEvent.ApiConnected, { | ||
value: 1, | ||
apiIndex: i, | ||
endpoint: endpoint, | ||
}); | ||
}); | ||
api.on('disconnected', () => { | ||
this.eventEmitter.emit(IndexerEvent.ApiConnected, { | ||
value: 0, | ||
apiIndex: i, | ||
endpoint: endpoint, | ||
|
||
api.on('connected', () => { | ||
this.eventEmitter.emit(IndexerEvent.ApiConnected, { | ||
value: 1, | ||
apiIndex: i, | ||
endpoint: endpoint, | ||
}); | ||
}); | ||
}); | ||
|
||
if (!this.networkMeta) { | ||
this.networkMeta = connection.networkMeta; | ||
|
||
if ( | ||
network.chainId && | ||
network.chainId !== this.networkMeta.genesisHash | ||
) { | ||
const err = new Error( | ||
`Network chainId doesn't match expected genesisHash. Your SubQuery project is expecting to index data from "${ | ||
network.chainId ?? network.genesisHash | ||
}", however the endpoint that you are connecting to is different("${ | ||
this.networkMeta.genesisHash | ||
}). Please check that the RPC endpoint is actually for your desired network or update the genesisHash.`, | ||
); | ||
logger.error(err, err.message); | ||
throw err; | ||
} | ||
} else { | ||
const genesisHash = api.genesisHash.toString(); | ||
if (this.networkMeta.genesisHash !== genesisHash) { | ||
throw this.metadataMismatchError( | ||
'Genesis Hash', | ||
this.networkMeta.genesisHash, | ||
genesisHash, | ||
); | ||
api.on('disconnected', () => { | ||
this.eventEmitter.emit(IndexerEvent.ApiConnected, { | ||
value: 0, | ||
apiIndex: i, | ||
endpoint: endpoint, | ||
}); | ||
}); | ||
|
||
if (!this.networkMeta) { | ||
this.networkMeta = connection.networkMeta; | ||
|
||
if ( | ||
network.chainId && | ||
network.chainId !== this.networkMeta.genesisHash | ||
) { | ||
const err = new Error( | ||
`Network chainId doesn't match expected genesisHash. Your SubQuery project is expecting to index data from "${ | ||
network.chainId ?? network.genesisHash | ||
}", however the endpoint that you are connecting to is different("${ | ||
this.networkMeta.genesisHash | ||
}). Please check that the RPC endpoint is actually for your desired network or update the genesisHash.`, | ||
); | ||
logger.error(err, err.message); | ||
throw err; | ||
} | ||
} else { | ||
const genesisHash = api.genesisHash.toString(); | ||
if (this.networkMeta.genesisHash !== genesisHash) { | ||
throw this.metadataMismatchError( | ||
'Genesis Hash', | ||
this.networkMeta.genesisHash, | ||
genesisHash, | ||
); | ||
} | ||
} | ||
} | ||
|
||
endpointToApiIndex[endpoint] = connection; | ||
endpointToApiIndex[endpoint] = connection; | ||
} catch (e) { | ||
logger.error(`failed to init ${endpoint}: ${e}`); | ||
endpointToApiIndex[endpoint] = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move some of this to node core? Its pretty much the same on all chains |
||
} | ||
} | ||
|
||
await this.connectionPoolService.addBatchToConnections(endpointToApiIndex); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to add
initFailed
can't we use this?