Skip to content

Commit

Permalink
fix(server): make eviction work
Browse files Browse the repository at this point in the history
  • Loading branch information
jrea committed May 21, 2024
1 parent b4e692b commit f3ca437
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 14 deletions.
6 changes: 4 additions & 2 deletions packages/server/src/Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const init = (config: Config): Api => {
export class Server {
config: Config;
api: Api;
private manager: DbManager;
private manager!: DbManager;
private servers: Map<string, Server>;

constructor(config?: ServerConfig) {
Expand Down Expand Up @@ -61,8 +61,10 @@ export class Server {
...cfg,
});
this.setConfig(updatedConfig);

this.manager.clear(this.config);
this.manager = new DbManager(this.config);
this.api = init(updatedConfig);
this.api = init(this.config);
return this;
}

Expand Down
35 changes: 27 additions & 8 deletions packages/server/src/db/DBManager.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Pool } from 'pg';

import { Config } from '../utils/Config';
import { watchEvictPool } from '../utils/Event';
import { closeEvictPool, watchEvictPool } from '../utils/Event';
import Logger from '../utils/Logger';
import { ServerConfig } from '../types';

import NileDatabase from './NileInstance';

export default class DBManager {
connections: Map<string, NileDatabase>;
cleared: boolean;

private makeId(
tenantId?: string | undefined | null,
Expand All @@ -24,31 +25,49 @@ export default class DBManager {
}
constructor(config: ServerConfig) {
const { info } = Logger(config, '[DBManager]');
this.cleared = false;
this.connections = new Map();
// add the base one, so you can at least query
const id = this.makeId();
info('constructor', id);
this.connections.set(id, new NileDatabase(new Config(config), id));
watchEvictPool((id) => {
if (id && this.connections.has(id)) {
this.connections.delete(id);
}
});
watchEvictPool(this.poolWatcher(config));
}
poolWatcher = (config: ServerConfig) => (id: undefined | null | string) => {
const { info } = Logger(config, '[DBManager]');
if (id && this.connections.has(id)) {
info('Removing', id, 'from db connection pool.');
this.connections.delete(id);
}
};

getConnection(config: ServerConfig): Pool {
getConnection = (config: ServerConfig): Pool => {
const { info } = Logger(config, '[DBManager]');
const id = this.makeId(config.tenantId, config.userId);
const existing = this.connections.get(id);
info('# of instances:', this.connections.size);
if (existing) {
info('returning existing', id);
existing.startTimeout();
return existing.pool;
}
const newOne = new NileDatabase(new Config(config), id);
this.connections.set(id, newOne);
info('created new', id);
info('# of instances:', this.connections.size);
// resume listening to the evict pool if a connection is requested.
if (this.cleared) {
this.cleared = false;
watchEvictPool(this.poolWatcher(config));
}
return newOne.pool;
}
};

clear = (config: ServerConfig) => {
const { info } = Logger(config, '[DBManager]');
info('Clearing all connections', this.connections.size);
closeEvictPool(this.poolWatcher(config));
this.cleared = true;
this.connections.clear();
};
}
17 changes: 16 additions & 1 deletion packages/server/src/db/NileInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,34 @@ class NileDatabase {
_client.release();
}
});

this.startTimeout();
});
this.pool.on('error', async (e) => {
info('pool failed', e);
if (this.timer) {
clearTimeout(this.timer);
}
evictPool(this.id);
});
}

startTimeout() {
const { info } = Logger(this.config, '[NileInstance]');
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(async () => {
await this.pool.end();
info(
'Pool reached idleTimeoutMillis.',
this.id,
'evicted after',
this.config.db.idleTimeoutMillis,
'ms'
);
await this.pool.end(() => {
// something odd going on here. Without the callback, pool.end() is flakey
});
evictPool(this.id);
}, this.config.db.idleTimeoutMillis);
}
Expand Down
8 changes: 7 additions & 1 deletion packages/server/src/db/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import NileDB from './index';

const properties = ['connections'];
const properties = [
'connections',
'clear',
'cleared',
'getConnection',
'poolWatcher',
];
describe('db', () => {
it('has expected properties', () => {
const db = new NileDB({
Expand Down
17 changes: 15 additions & 2 deletions packages/server/src/utils/Config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,22 @@ export class Config {
constructor(config?: ServerConfig, logger?: string) {
const envVarConfig: EnvConfig = { config, logger };

this.databaseId = getDatbaseId(envVarConfig) as string;
this.user = getUsername(envVarConfig) as string;
this.password = getPassword(envVarConfig) as string;
if (process.env.NODE_ENV !== 'TEST') {
if (!this.user) {
throw new Error(
'User is required. Set NILEDB_USER as an environment variable or set `user` in the config options.'
);
}
if (!this.password) {
throw new Error(
'Password is required. Set NILEDB_PASSWORD as an environment variable or set `password` in the config options.'
);
}
}

this.databaseId = getDatbaseId(envVarConfig) as string;
this.databaseName = getDatabaseName(envVarConfig) as string;
this._tenantId = getTenantId(envVarConfig);
this.debug = Boolean(config?.debug);
Expand Down Expand Up @@ -169,7 +182,7 @@ export class Config {
throw new Error('HTTP error has occured');
} else {
throw new Error(
'Unable to auto-configure. Please set or remove NILEDB_API, NILEDB_NAME, and NILEDB_HOST in your .env file.'
'Unable to auto-configure. Please remove NILEDB_NAME, NILEDB_API_URL, NILEDB_POSTGRES_URL, and/or NILEDB_HOST from your environment variables.'
);
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/server/src/utils/Event/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class Eventer {
// store the callback function of the subscriber
this.events[eventName].push(callback);
}

unsubscribe(eventName: string, callback: EventFn) {
const toRemove = this.events[eventName].findIndex((cb) => cb === callback);
if (toRemove !== -1) {
this.events[eventName].splice(toRemove, 1);
}
}
}

// tenantId manager
Expand All @@ -58,6 +65,10 @@ export const watchToken = (cb: EventFn) => eventer.subscribe(Events.Token, cb);

export const watchEvictPool = (cb: EventFn) =>
eventer.subscribe(Events.EvictPool, cb);

export const closeEvictPool = (cb: EventFn) =>
eventer.unsubscribe(Events.EvictPool, cb);

export const evictPool = (val: BusValues) => {
eventer.publish(Events.EvictPool, val);
};

0 comments on commit f3ca437

Please sign in to comment.