node-pg-pool icon indicating copy to clipboard operation
node-pg-pool copied to clipboard

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

Open claytongulick opened this issue 7 years ago • 8 comments

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?

claytongulick avatar Feb 20 '18 02:02 claytongulick

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.

charmander avatar Feb 20 '18 16:02 charmander

@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?

claytongulick avatar Feb 21 '18 21:02 claytongulick

@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.

charmander avatar Feb 21 '18 21:02 charmander

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.

msakrejda avatar Apr 08 '18 18:04 msakrejda

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 avatar Jun 08 '18 12:06 mathroc

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

mathroc avatar Jun 08 '18 21:06 mathroc

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

charmander avatar Jun 08 '18 22:06 charmander

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)

mathroc avatar Jun 08 '18 22:06 mathroc