Skip to content

Commit 4d71970

Browse files
authored
Make error handling more explicit, and warn user if they haven't (#510)
2 parents c355616 + caa2264 commit 4d71970

File tree

3 files changed

+76
-11
lines changed

3 files changed

+76
-11
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ Read more:
3131
clearer that it's for use in one-off locations (some felt the "quick" referred
3232
to the speed it executed, rather than the amount of effort required from the
3333
programmer)
34+
- We'll now warn you if you haven't installed error handlers on the pool, and
35+
will only install them ourself if needed
3436
- Fixes bug where CLI defaults override `graphile.config.js` settings (by
3537
removing CLI defaults)
3638
- Fix bug where executable tasks had their stdout/stderr ignored; this is now

src/lib.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as assert from "assert";
22
import { EventEmitter } from "events";
33
import { applyHooks, AsyncHooks, resolvePresets } from "graphile-config";
4-
import { Client, Pool, PoolClient } from "pg";
4+
import { Client, Pool, PoolClient, PoolConfig } from "pg";
55

66
import { makeWorkerPresetWorkerOptions } from "./config";
77
import { migrations } from "./generated/sql";
@@ -284,7 +284,6 @@ export async function assertPool(
284284
releasers: Releasers,
285285
): Promise<Pool> {
286286
const {
287-
logger,
288287
resolvedPreset: {
289288
worker: { maxPoolSize, connectionString },
290289
},
@@ -299,28 +298,50 @@ export async function assertPool(
299298
let pgPool: Pool;
300299
if (_rawOptions.pgPool) {
301300
pgPool = _rawOptions.pgPool;
301+
if (pgPool.listeners("error").length === 0) {
302+
console.warn(
303+
`Your pool doesn't have error handlers! See: https://err.red/wpeh`,
304+
);
305+
installErrorHandlers(compiledSharedOptions, releasers, pgPool);
306+
}
302307
} else if (connectionString) {
303-
pgPool = new Pool({
308+
pgPool = makeNewPool(compiledSharedOptions, releasers, {
304309
connectionString,
305310
max: maxPoolSize,
306311
});
307-
releasers.push(() => {
308-
pgPool.end();
309-
});
310312
} else if (process.env.PGDATABASE) {
311-
pgPool = new Pool({
313+
pgPool = makeNewPool(compiledSharedOptions, releasers, {
312314
/* Pool automatically pulls settings from envvars */
313315
max: maxPoolSize,
314316
});
315-
releasers.push(() => {
316-
pgPool.end();
317-
});
318317
} else {
319318
throw new Error(
320319
"You must either specify `pgPool` or `connectionString`, or you must make the `DATABASE_URL` or `PG*` environmental variables available.",
321320
);
322321
}
323322

323+
return pgPool;
324+
}
325+
326+
function makeNewPool(
327+
compiledSharedOptions: CompiledSharedOptions,
328+
releasers: Releasers,
329+
poolOptions: PoolConfig,
330+
) {
331+
const pgPool = new Pool(poolOptions);
332+
releasers.push(() => {
333+
pgPool.end();
334+
});
335+
installErrorHandlers(compiledSharedOptions, releasers, pgPool);
336+
return pgPool;
337+
}
338+
339+
function installErrorHandlers(
340+
compiledSharedOptions: CompiledSharedOptions,
341+
releasers: Releasers,
342+
pgPool: Pool,
343+
) {
344+
const { logger } = compiledSharedOptions;
324345
const handlePoolError = (err: Error) => {
325346
/*
326347
* This handler is required so that client connection errors on clients
@@ -358,7 +379,6 @@ export async function assertPool(
358379
pgPool.removeListener("error", handlePoolError);
359380
pgPool.removeListener("connect", handlePoolConnect);
360381
});
361-
return pgPool;
362382
}
363383

364384
export type Release = () => PromiseOrDirect<void>;

website/src/pages/errors/peh.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Pool error handling
2+
3+
You're likely here because you received an error such as:
4+
5+
```
6+
Your pool doesn't have error handlers! See: https://err.red/wpeh
7+
```
8+
9+
This means you've passed your own
10+
[pg.Pool instance](https://node-postgres.com/apis/pool) to Graphile Worker, but
11+
that pool did not have error handling installed.
12+
13+
If an error occurs on the pool and there is no event handler installed Node
14+
would exit
15+
[as described in the Node.js docs](https://nodejs.org/api/events.html#error-events).
16+
You get a network interruption, and your Worker might crash!
17+
18+
Don't worry, we've installed error handlers for you, but that's not ideal - you
19+
should be handling this yourself. To do so, use code like this:
20+
21+
```ts
22+
// Handle errors on the pool directly
23+
pgPool.on("error", (err) => {
24+
console.error(`PostgreSQL idle client generated error: ${err.message}`);
25+
});
26+
// Handle errors on the client when it's checked out of the pool but isn't
27+
// actively being used
28+
pgPool.on("connect", (client) => {
29+
client.on("error", (err) => {
30+
console.error(`PostgreSQL active client generated error: ${err.message}`);
31+
});
32+
});
33+
```
34+
35+
Your code just needs to make sure the 'error' events on pool and client have
36+
handlers installed; the handlers don't actually have to _do_ anything - they
37+
could be NO-OPs.
38+
39+
Typically these kinds of errors would occur when e.g. the connection between
40+
Node.js and PostgreSQL is interrupted (including when the PostgreSQL server
41+
shuts down). In these cases since the client is likely not actively being
42+
`await`-ed the error will have no handler, and will result in a process exit if
43+
unhandled.

0 commit comments

Comments
 (0)