Sitelet https://web.archive.org/web/20200716123617/https://github.com/knex/knex/issues/3578
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

expirationChecker won't recreate connection object with Postgres client #3578

Open
dsbrgg opened this issue Dec 11, 2019 · 13 comments
Open

expirationChecker won't recreate connection object with Postgres client #3578

dsbrgg opened this issue Dec 11, 2019 · 13 comments

Comments

@dsbrgg
Copy link

@dsbrgg dsbrgg commented Dec 11, 2019

Environment

Knex version: 0.20.3
Database + version: psql (PostgreSQL) 12.1
OS: macOS 10.14.6

Bug

The expirationChecker property on the connection object is not being called to recreate the connection object with new credentials for the database, even though the timeout was reached.

The first time that a request is being made, it works fine but after the timeout, it won't allow to make subsequent connections to the database returning a permission_denined error (as it should). Here's an example of the knex instance being made.

const database = knex({
  ...configuration,
  connection: async () => {
    const { timeout, ...credentials } = await vault.psqlCredentials('my-role');

    const connection = {
      ...configuration.connection,
      ...credentials,
      expirationChecker: () => timeout <= Date.now(),
      ssl: yn(get(configuration, 'connection.ssl', false))
    };

    return connection;
  }
});

It came to my attention that the psql config interface does not have an expirationChecker property in it but, I'm not sure if this has a direct impact with this situation since the function that would update the connection seems to be called on the first time.

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 15, 2020

So, after a couple of digging, I think I was able to find the issue and at least provide some kind of solution, even if a naive one.

The connectionConfigExpirationChecker property is not being used except for the first time. That is happening because it's only being called on the first create call on tarn.js.

From what I've investigated, tarn.js does not have a property or something like that to destroy resources based on expiration.

Basically, I've just added a setTimeout on the updatePoolConnectionSettingsFromProvider and acquiring the resource on the pool to properly close the connection. This also implies that the expirationChecker function should return a value representing the delay in milliseconds to be passed to setTimeout.

I've done some local tests here with the codebase I'm working with and I finally had the behaviour I was expecting without any side-effects from what I can see.

@kibertoad , @oranoran , @Ali-Dalal - maybe you could give some insight in this since you guys were involved in the last PR related to this functionality.

@elhigu
Copy link
Member

@elhigu elhigu commented Jan 20, 2020

I actually don't like or understand why there even is expirationChecker attribute in connection. To me would be better if connection could be declared as a function which then may return config directly or it may return promise, which resolves config.

Then it would be up to client code to implement logic when it wants to get fresh config and when it wants to for example return the same already resolved promise for the old config.

Could someone explain me better how that async / function getting of connection parameters works nowadays or what am I missing here?

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 21, 2020

@elhigu I believe that this expirationChecker was introduced along with the PR that I've mentioned, that also introduced the async connection.

I can't tell for every case but at least in my case, this property expirationChecker would be very handy because I need to get new credentials for the database after a set amount of time and I believe this way would be easier since knex could just end the resource on its own.

I believe that the async connection is actually being used as it should but the issue is that, there's in fact a property expirationChecker which seems to not be used at all, besides the first time that tarn.js actually creates the resource.

@Ali-Dalal
Copy link

@Ali-Dalal Ali-Dalal commented Jan 22, 2020

@dsbrgg to be honest, I haven't tried the expirationChecker because, in my cause, It is not needed. But setting the connection config as an async function works like a charm.

@oranoran can you put your resolution on this?

@oranoran
Copy link
Contributor

@oranoran oranoran commented Jan 22, 2020

@dsbrgg is your expectation for the configuration to expire at the timeout, or for actual living connections that were created using it to expire?
The intention was to implement the former not the latter.
The implementation has nothing to do with tarn, it's part of the client code that creates new connections and isn't related to maintaining them once created.

@elhigu
Copy link
Member

@elhigu elhigu commented Jan 22, 2020

I can't tell for every case but at least in my case, this property expirationChecker would be very handy because I need to get new credentials for the database after a set amount of time and I believe this way would be easier since knex could just end the resource on its own.

That would be as easy or even easier if knex would just always reload config if it is changed. Then there would be no need for that kind of timer. So to me that parameter still looks like a hack to me instead of feature. I remember that async knex configuration feature first implementation was not very versatile for many use cases either (IIRC it only made possible to get async config once and then there was no way to update it afterwards)...

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 22, 2020 •

@Ali-Dalal Yes, like I said, the async config works fine. My problem has been with the expirationChecker exclusively.

@oranoran I suppose both. Since the connection would have to expire after a timeout, the resource would have to be recreated or you would end up with an invalid knex instance - which is my case, after the timeout, I get invalid credentials errors.

Regardless, the expirationChecker is just being called on the resource creation. If this is what the property was actually intended to do, then I got it wrong. I thought it would be checking if the connection was still valid somehow.

@elhigu I'm sorry but I don't understand what you mean by "reloading the config if it's changed". The timer was just a naive approach to the matter as I was under the impression that it was supposed to verify if the connection was valid from time to time or before requests were made and then call the async connection function again.

@elhigu
Copy link
Member

@elhigu elhigu commented Jan 22, 2020 •

I'm sorry but I don't understand what you mean by "reloading the config if it's changed".

I mean something like this:

let config = {};
setInterval(() => {
  config = generateNewConfig();
}, 5000); 

{
  client: 'pg',
  connection: () => {
     return config;
  }
}

That would cause knex to reload every 5 seconds, since returned configuration object is changing every 5 seconds (of course connection callback could also return a promise which would be asynchronously resolved).

I was under the impression that it was supposed to verify if the connection was valid from time to time or before requests were made and then call the async connection function again.

I'm pretty sure that it does not check if the connection details are valid, but only re-reads new config with that interval.

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 22, 2020

@elhigu Thanks for the example. I guess this could do it.

I'm pretty sure that it does not check if the connection details are valid, but only re-reads new config with that interval.

I think this is not actually happening. At least from what I've tested, even after the expiration was passed, connection was not being called again to re-read the config/update the config details.

That's what I mean when I say that the connection is valid or not, since the expiration, for me, means that at that point that the connection would no longer be valid.

It is called once when the connection is first being created and never again to see if the expirationChecker will return true. If that's the intended behaviour than my bad, I misunderstood the point of the property.

Anyway, I'll try something around that example you've showed.

@elhigu
Copy link
Member

@elhigu elhigu commented Jan 24, 2020

Anyway, I'll try something around that example you've showed.

I don't think that my example will work at all, since it is an imaginary API how I would have wanted that configuration reloading feature to work.

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 24, 2020 •

Oh sorry, I thought you meant as if that could solve it.

Could this function be called upon the validation of a connection?

This way the connection could be destroyed and also the connection would be rebuilt at that point. This way it would prevent any type of timer.

I suppose the property connectionConfigExpirationChecker is set to true by default on initialisation, so maybe a variable that would contain the function for expirationChecker and will be checked and called upon validate, maybe something along these lines. I've tried some tests here and it worked as well.

@elhigu
Copy link
Member

@elhigu elhigu commented Jan 24, 2020

Maybe... more people should read through those connection parts of the code base to make sure that it is a valid solution... I don't know when I would have time to concentrate on that.

@dsbrgg
Copy link
Author

@dsbrgg dsbrgg commented Jan 27, 2020

I don't need this immediately as I came up with this issue for an upcoming feature on my project but it would be great if this could be implemented. Thanks for the responses as well @elhigu!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.