brianc / node-pg-pool

A connection pool for node-postgres
MIT License
180 stars 64 forks source link

Race condition in pool.on('connect',...) ? #97

Open claytongulick opened 6 years ago

claytongulick commented 6 years ago

A (fairly) common question I see around is how to set options like search_path with node-pg. The answer I see most commonly given is to use something like

pool.on('connect', (client) => {client.query('set search_path...');});

Glancing through the code though, it doesn't look like doing this will actually guarantee that the search_path will be set before a client issues a query, given that it's an async call.

Are we running the risk that queries can be executed prior to setup? This could be a potential security issue as well if using RLS and something like 'SET my.user_id = 123' - a query could potentially be executed prior to the SET command completing?

charmander commented 6 years ago

Event listeners are called synchronously by emit, and the pool emits connect on a client before the client is used, so if the listener makes a query immediately (like here) there’s no race condition compared to queries after the client has been acquired. It’s not an elegant solution, however, and the query queue that allows this to work is a bit of a pain – which is why I’d prefer to expose https://github.com/brianc/node-postgres/issues/1393#issuecomment-319308021.

claytongulick commented 6 years ago

@charmander I glanced through the source, and given that the db call is async, I didn't see any guarantee that a call like I have in my example would complete prior to a client issuing a query. Did I miss something?

charmander commented 6 years ago

@claytongulick A client can only run one query at a time, so pg keeps a queue of queries for each client. If the query is first to enter the queue, it will be the first to run.

msakrejda commented 6 years ago

Thanks for clarifying that @charmander -- I came here with the same concern as @claytongulick and found this issue, but what you're saying makes sense.

mathroc commented 6 years ago

it's true that you can guarantee the query to be executed first, but, only if the connect callback is synchronous, if your callback uses await/promises before issuing the query, the order won't be guaranteed

I'm currently looking for a workaround for this

also, I think sometimes (eg, for SET my.user_id = 123) I think the correct event to listen to is acquire because the pool could potentially return an already connected client (despite the function being called connect). to be confirmed, I'm not 100% certain

mathroc commented 6 years ago

well there is the undocumented options.verify that could be used for on connect, but it won't work for acquired: options.verify is only called for new clients and not when they are reused.

so I resorted to monkey patching the connect method for now

charmander commented 6 years ago

@mathroc What is it that you need to do every time a connection is acquired?

mathroc commented 6 years ago

it's a websocket application, I call select set_config('session.id', $1::text, false) with the session id corresponding to socket I'm working with (it's used by row level security rules)