Skip to content

Commit

Permalink
fix: attempts to connect the correct number of times (fixes #473)
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Sep 27, 2023
1 parent 8f066eb commit 503618f
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 59 deletions.
61 changes: 3 additions & 58 deletions src/factories/createConnection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { bindPoolConnection } from '../binders/bindPoolConnection';
import { ConnectionError, UnexpectedStateError } from '../errors';
import { getPoolClientState, getPoolState, poolClientStateMap } from '../state';
import { UnexpectedStateError } from '../errors';
import { establishConnection } from '../routines/establishConnection';
import { getPoolClientState, getPoolState } from '../state';
import {
type ClientConfiguration,
type Connection,
Expand All @@ -10,9 +11,7 @@ import {
type MaybePromise,
type QuerySqlToken,
} from '../types';
import { createUid } from '../utilities/createUid';
import { type Pool as PgPool, type PoolClient as PgPoolClient } from 'pg';
import { serializeError } from 'serialize-error';

type ConnectionHandlerType = (
connectionLog: Logger,
Expand Down Expand Up @@ -51,60 +50,6 @@ const destroyBoundConnection = (boundConnection: DatabasePoolConnection) => {
}
};

const establishConnection = async (
parentLog: Logger,
pool: PgPool,
connectionRetryLimit: number,
) => {
const poolState = getPoolState(pool);

let connection: PgPoolClient;

let remainingConnectionRetryLimit = connectionRetryLimit;

// eslint-disable-next-line no-constant-condition
while (true) {
remainingConnectionRetryLimit--;

try {
connection = await pool.connect();

poolClientStateMap.set(connection, {
connectionId: createUid(),
mock: poolState.mock,
poolId: poolState.poolId,
terminated: null,
transactionDepth: null,
transactionId: null,
});

break;
} catch (error) {
parentLog.error(
{
error: serializeError(error),
remainingConnectionRetryLimit,
},
'cannot establish connection',
);

if (remainingConnectionRetryLimit > 1) {
parentLog.info('retrying connection');

continue;
} else {
throw new ConnectionError(error.message);
}
}
}

if (!connection) {
throw new UnexpectedStateError('Connection handle is not present.');
}

return connection;
};

export const createConnection = async (
parentLog: Logger,
pool: PgPool,
Expand Down
60 changes: 60 additions & 0 deletions src/routines/establishConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ConnectionError, UnexpectedStateError } from '../errors';
import { getPoolState, poolClientStateMap } from '../state';
import { type Logger } from '../types';
import { createUid } from '../utilities/createUid';
import { type Pool as PgPool, type PoolClient as PgPoolClient } from 'pg';
import { serializeError } from 'serialize-error';

export const establishConnection = async (
parentLog: Logger,
pool: PgPool,
connectionRetryLimit: number,
) => {
const poolState = getPoolState(pool);

let connection: PgPoolClient;

let remainingConnectionRetryLimit = connectionRetryLimit + 1;

// eslint-disable-next-line no-constant-condition
while (true) {
remainingConnectionRetryLimit--;

try {
connection = await pool.connect();

poolClientStateMap.set(connection, {
connectionId: createUid(),
mock: poolState.mock,
poolId: poolState.poolId,
terminated: null,
transactionDepth: null,
transactionId: null,
});

break;
} catch (error) {
parentLog.error(
{
error: serializeError(error),
remainingConnectionRetryLimit,
},
'cannot establish connection',
);

if (remainingConnectionRetryLimit > 1) {
parentLog.info('retrying connection');

continue;
} else {
throw new ConnectionError(error.message);
}
}
}

if (!connection) {
throw new UnexpectedStateError('Connection handle is not present.');
}

return connection;
};
2 changes: 1 addition & 1 deletion src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type TypeOverrides } from './types';
import { type DeferredPromise } from './utilities/defer';
import { type Pool as PgPool, type PoolClient as PgClientPool } from 'pg';

type PoolState = {
export type PoolState = {
ended: boolean;
mock: boolean;
poolId: string;
Expand Down
46 changes: 46 additions & 0 deletions test/slonik/routines/establishConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Logger } from '../../../src/Logger';
import { establishConnection } from '../../../src/routines/establishConnection';
import { type PoolState, poolStateMap } from '../../../src/state';
import test from 'ava';
import { type Pool as PgPool } from 'pg';
import * as sinon from 'sinon';

test('attempts to connection X times', async (t) => {
const pool = {
connect: sinon.stub(),
};

poolStateMap.set(pool as unknown as PgPool, {} as unknown as PoolState);

const connectionRetryLimit = 3;

await t.throwsAsync(
establishConnection(
Logger,
pool as unknown as PgPool,
connectionRetryLimit,
),
);

t.is(pool.connect.callCount, connectionRetryLimit);
});

test('does not attempt to retry connection when set to 0', async (t) => {
const pool = {
connect: sinon.stub(),
};

poolStateMap.set(pool as unknown as PgPool, {} as unknown as PoolState);

const connectionRetryLimit = 0;

await t.throwsAsync(
establishConnection(
Logger,
pool as unknown as PgPool,
connectionRetryLimit,
),
);

t.is(pool.connect.callCount, 1);
});

0 comments on commit 503618f

Please sign in to comment.