Skip to content
This repository was archived by the owner on Dec 30, 2019. It is now read-only.

Conversation

@brianc
Copy link
Owner

@brianc brianc commented Jul 9, 2017

This contains a massive number of internal changes. However, the external API remains very close to the same.

The breaking changes are:

  • Drop support for node < 4.x. closes drop support of node <4 ? #56
  • No longer use node-pool as a dependency so any configuration options you previously passed to node-pool will not work anymore except for max and idleTimeoutMillis
  • Cannot call pool constructor without new keyword

I attempted to upgrade to node-pool@3.x, but it weirdly no longer supports returning an error when a client fails to connect. Instead it tries to reconnect the client in a tight loop and makes the error non-actionable. I was unable to find a good way to hook into this behavior and override it. Furthermore it has a lot of additional bells and whistles for priority queuing, min levels, auto-connection, and other behavior pg and pg-pool have never used. Initially I was distraught at the thought of implementing the pooling behavior here, but it turns out it wasn't very much code & all the tests still pass!

After this I spent some time going through and writing tests for many of the open issues to verify they no longer happen. I also added support for the long requested connectionTimeoutMillis - now supplying connectionTimeoutMillis as a millisecond value will cause the pool to return an error on your connect call if a connection could not be established within the configured time. By default there is no timeout.

Known fixes:

Copy link

@calebboyd calebboyd left a comment

Choose a reason for hiding this comment

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

Neat! I'm excited to see that the pool is being dropped.

I did notice that promise support is still a bit second class. In that -- promisified apis would be able to perform much better than the scheme currently used.

index.js Outdated
this.emit('error', e, client)
}.bind(this))
function release (client, err) {
client.release = function () { throw new Error('called release twice') }

Choose a reason for hiding this comment

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

This function could be hoisted

Copy link
Owner Author

Choose a reason for hiding this comment

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

good call. done.

index.js Outdated
this._pulseQueue()
}

Pool.prototype.connect = function (cb) {

Choose a reason for hiding this comment

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

It looks like the callback in the new version may be unintentionally synchronous where the old version was async

Copy link
Owner Author

Choose a reason for hiding this comment

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

Is that bad? It's a semver major bump & I'd rather not introduce a nextTick just to keep it async.


const describe = require('mocha').describe
const it = require('mocha').it
const BluebirdPromise = require('bluebird')

Choose a reason for hiding this comment

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

BluebirdPromise.coroutine == co.wrap if you want to drop a dep

Copy link
Owner Author

Choose a reason for hiding this comment

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

nice! today I learned!

index.js Outdated
reject(err)
}
return
return cb(err, undefined, function () { })

Choose a reason for hiding this comment

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

I think this block is unreachable based on the use of _pendingQueue

index.js Outdated
this.log('ending')
if (this.ending) {
const err = new Error('Called end on pool more than once')
return cb ? cb(err) : this.Promise.reject(err)

Choose a reason for hiding this comment

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

sync : async

Copy link
Owner Author

Choose a reason for hiding this comment

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

Is it bad to do sync if its a callback and async if it's a promise? Promises are always async, but callbacks done necessarily have to be and AFAIK there's never been an idiom saying they should be.

Choose a reason for hiding this comment

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

In general, Its not good to mix async and sync in the same API. Whats weird here is nothing invoked internally is actually async except the result. In one case I can get the error synchronously (looks async) and the other, async.

If I were to express a preference it would be to stick with an async api throughout.

Here is a tree of posts that go into a lot more detail http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony

Copy link
Owner Author

Choose a reason for hiding this comment

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

Good point - I'll fix this

index.js Outdated
return promised.result
}

Object.defineProperty(Pool.prototype, 'waitingCount', {

Choose a reason for hiding this comment

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

Could clean this up a lot if the export was switched to something like:

module.exports = function (options, client) {
    return new PgPool(options, client)
}

Copy link
Owner Author

Choose a reason for hiding this comment

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

I'm not following how this would be cleaned up? Could you expand on what you meant here?

Choose a reason for hiding this comment

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

np, I just mean taking advantage of the class sugar for function defs and getters of the new min node version =)

class PgPool {
  _isFull() {..}
  ...
  connect() {...}
  ...  
  get waitingCount() {...}
  get totalCount() {...}
  get idleCount() {...}
}

module.exports = function (options, client) {
   return new PgPool(options, client)
}
```

Copy link
Owner Author

Choose a reason for hiding this comment

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

I'll try that & see if it works in node 4. If so, definitely will go this way.

index.js Outdated
const response = this._promisify(cb)
const result = response.result
cb = response.callback
this._pendingQueue.push((err, client) => {

Choose a reason for hiding this comment

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

proposal: instead of handling this in a closure -- just store the waiter and handle this when invoking it during dequeue/shift

Copy link
Owner Author

Choose a reason for hiding this comment

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

excellent suggestion! cleaned this up.

- node_js: "6"
addons:
postgresql: "9.4"
- node_js: "8"

Choose a reason for hiding this comment

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

Just wonder : why remove node 8 ?

Copy link
Owner Author

Choose a reason for hiding this comment

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

it was messing up in travis but working locally even though the same tests were passing locally - I just removed it temporarily so I could make sure the other tests were passing. I'll need to get it working in travis eventually.

@brianc
Copy link
Owner Author

brianc commented Jul 10, 2017

Thank you so much for the feedback and review! I'm not quite done w/ this branch yet - code's still a bit dirty...but have quite a bit of client work in the next few days so I'll take me a bit of time to get to this. But know I'm grateful & I'll be back within a day or two! 💃

@brianc
Copy link
Owner Author

brianc commented Jul 11, 2017

I did notice that promise support is still a bit second class. In that -- promisified apis would be able to perform much better than the scheme currently used.

@calebboyd - using callbacks internally in this module is so we can support pg>=5.x instead of pg@7 which is when all the APIs for pg are fully promisified. What do you mean by that exactly - could you elaborate?

@calebboyd
Copy link

calebboyd commented Jul 12, 2017

Mostly I think that it would be best to stick with one API or the other.
My reasoning is that: accepting a custom Promise at this level would cut into performance.

For example, Bluebird.promisify, will rewrite the function (emit new code) in order to maintain low overhead when allocating new Promises -- and then uses an internal implementation. Node's require('util').promisify will use a native deferred api. Each of which would behave much faster than creating the new Promise and deferred mechanism being used. As a plus, plain callbacks for waiters in this internal queue will always be the quicker solution of the two.

In general sticking with callbacks here will allow people who use promises (I'm one) to do so with the lowest overhead, which is a plus for the library that proxies queries.

@calebboyd
Copy link

I guess that wasn't super clear. I don't have anything that would stop me from using this (I could promisify it anyways to avoid the issue). But Maybe the removal of this.Promise such that promisify is encouraged instead. As that is generally blessed now (in node core)

@brianc
Copy link
Owner Author

brianc commented Jul 12, 2017

I'll take a look at hard look at the code once I finish it to ensure it's not doing unnecessary work wrt allocating promises. It should only allocate a promise if you don't supply a callback: if you use a callback it will never even allocate any promises so you don't have to be concerned about any performance as some folks who use callbacks tend to be. 🙅

As far as Bluebird.promsify versus the ability to accept a callback or return a promise (the api that this lib is going for) - I'd be pretty surprised if Bluebird.promsify generates any noticeable performance gains over what's going on in this code, but I'll investigate that to see...and like you said if someone prefers that approach nothing is stopping them from doing that as well in their own code on top of the pool. 👍

The goal is to support both promises and callbacks in a transparent way versus requiring someone run Bluebird.promsify or something similar on the pool, at least for the time being: supply a callback & callbacks will be used. Don't & you'll get a promise back. 😄

Thanks again for the feedback & comments. They're very helpful! 👏

@calebboyd
Copy link

Thank you for entertaining them. I'm very grateful for your work on these tools 🔨

@brianc brianc merged commit a0eb36d into master Jul 14, 2017
@sehrope sehrope mentioned this pull request Aug 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Does idleTimeoutMillis = -1 keep connections open indefinently Fails to reconnect after invoking end() drop support of node <4 ? pg dependency

3 participants