Skip to content
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

Merged
merged 14 commits into from
Sep 6, 2023
Merged

handle failed api inits #1970

merged 14 commits into from
Sep 6, 2023

Conversation

guplersaxanoid
Copy link
Contributor

Description

Avoid exiting process when one of the API initialization fails. Instead mark it is as unusable and continue initializing other APIs.

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • I have tested locally
  • I have performed a self review of my changes
  • Updated any relevant documentation
  • Linked to any relevant issues
  • I have added tests relevant to my changes
  • Any dependent changes have been merged and published in downstream modules
  • My code is up to date with the base branch
  • I have updated relevant changelogs. We suggest using chan

Copy link
Collaborator

@stwiname stwiname left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the initial connection fails will it ever be retried again?

const poolItem: ConnectionPoolItem<T> = {
primary: primary,
performanceScore: 100,
failureCount: 0,
endpoint: endpoint,
backoffDelay: 0,
rateLimited: false,
failed: false,
failed: initFailed,
Copy link
Collaborator

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?

.filter((index) => !this.pool[index].initFailed);

if (initedIndices.length === 0) {
throw new Error(`Initialization failed for all endpoints. Please add healthier endpoints.`);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Comment on lines 134 to 162
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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

@guplersaxanoid
Copy link
Contributor Author

If the initial connection fails will it ever be retried again?

I'm not sure how to handle this when there are multiple workers involved. the initialization may fail in a subset of workers and we should only allow using an endpoint when all of the workers are connected to it.

@stwiname
Copy link
Collaborator

If the initial connection fails will it ever be retried again?

I'm not sure how to handle this when there are multiple workers involved. the initialization may fail in a subset of workers and we should only allow using an endpoint when all of the workers are connected to it.

Why would we only use an endpoint if all workers are connected? The shared state is the weighting not what endpoints can be used

Copy link
Collaborator

@stwiname stwiname left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking like a good improvement

): void {
if (attempt < 5) {
//eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth having a backoff function as we use the same concept a few places now. Also there needs to be a reference to this timeout

throw new Error(`Attempting to update connection that does not exist.`);
}

this.allApi[index] = api;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is safe? What happens if the wrong index is provided?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.) the order of the api will be different for different workers, making the connection pool state invalid because only the index is used to reference api.
2.) one api might overwrite another

so, it's important that we get the index right.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes its important to get the index right. Is using an int the right thing to key these by? If we use the endpoint it would be much safer

@@ -47,8 +48,9 @@ export class ApiService
connectionPoolService: ConnectionPoolService<ApiPromiseConnection>,
eventEmitter: EventEmitter2,
private nodeConfig: NodeConfig,
retryManager: RetryManager,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just meaning a util function. Adding another dependency ads a lot of unnecessary complexity.

Its already hard enough to write tests with ApiService because we need all the connectionPool stuff. We should be able to mock an api service without all that when we want a single endpoint. But thats another issue that shouldn't be addressed here

Copy link
Collaborator

@stwiname stwiname left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome! I think changing from index to endpoint was a good idea, it seems to mak code more readable

@stwiname
Copy link
Collaborator

I tried this by adding wss://fake.example.com/public-ws to the polkadot starter and the project wont start. The OnFinality endpoint is there still

});

try {
const {fulfilledIndex, result} = (await raceFulfilled(connectionPromises)) as {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a cast needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to rule out undefined

@stwiname stwiname merged commit 40a24d4 into main Sep 6, 2023
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants