Add debounce.cancel, cancel session cleanup
This commit is contained in:
parent
2256fbc1e8
commit
22f0b15e3a
@ -6,6 +6,7 @@ import {ITransactionManager} from '../database/ITransactionManager'
|
|||||||
import {Session as SessionEntity} from '../entities/Session'
|
import {Session as SessionEntity} from '../entities/Session'
|
||||||
import {SessionStore} from '../session/SessionStore'
|
import {SessionStore} from '../session/SessionStore'
|
||||||
import {UrlWithStringQuery} from 'url'
|
import {UrlWithStringQuery} from 'url'
|
||||||
|
import {apiLogger} from '../logger'
|
||||||
|
|
||||||
export interface ISessionOptions {
|
export interface ISessionOptions {
|
||||||
transactionManager: ITransactionManager,
|
transactionManager: ITransactionManager,
|
||||||
@ -32,7 +33,8 @@ export class SessionMiddleware implements IMiddleware {
|
|||||||
path: params.baseUrl.path,
|
path: params.baseUrl.path,
|
||||||
},
|
},
|
||||||
store: new SessionStore({
|
store: new SessionStore({
|
||||||
cleanup: 1,
|
cleanupDelay: 60 * 1000,
|
||||||
|
logger: apiLogger,
|
||||||
getRepository: this.getRepository,
|
getRepository: this.getRepository,
|
||||||
ttl: 1,
|
ttl: 1,
|
||||||
buildSession: this.buildSession,
|
buildSession: this.buildSession,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import request from 'supertest'
|
|||||||
import {SessionStore} from './SessionStore'
|
import {SessionStore} from './SessionStore'
|
||||||
import {ISession} from './ISession'
|
import {ISession} from './ISession'
|
||||||
import ExpressSession from 'express-session'
|
import ExpressSession from 'express-session'
|
||||||
|
import loggerFactory from '@rondo.dev/logger'
|
||||||
import {
|
import {
|
||||||
createConnection, Column, Connection, Entity, Index, PrimaryColumn,
|
createConnection, Column, Connection, Entity, Index, PrimaryColumn,
|
||||||
Repository,
|
Repository,
|
||||||
@ -51,7 +52,8 @@ describe('SessionStore', () => {
|
|||||||
maxAge: 10,
|
maxAge: 10,
|
||||||
},
|
},
|
||||||
store: new SessionStore({
|
store: new SessionStore({
|
||||||
cleanup: 1,
|
logger: loggerFactory.getLogger('api'),
|
||||||
|
cleanupDelay: 60 * 1000,
|
||||||
getRepository: () => repository,
|
getRepository: () => repository,
|
||||||
ttl: 1,
|
ttl: 1,
|
||||||
buildSession: (sd, s) => ({...s, extraData: 'test'}),
|
buildSession: (sd, s) => ({...s, extraData: 'test'}),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {Store} from 'express-session'
|
|||||||
import {ISession} from './ISession'
|
import {ISession} from './ISession'
|
||||||
import {Repository, LessThan} from 'typeorm'
|
import {Repository, LessThan} from 'typeorm'
|
||||||
import {debounce} from '@rondo.dev/tasq'
|
import {debounce} from '@rondo.dev/tasq'
|
||||||
|
import { ILogger } from '@rondo.dev/logger'
|
||||||
|
|
||||||
type SessionData = Express.SessionData
|
type SessionData = Express.SessionData
|
||||||
type Callback = (err?: any, session?: SessionData) => void
|
type Callback = (err?: any, session?: SessionData) => void
|
||||||
@ -9,8 +10,9 @@ type CallbackErr = (err?: any) => void
|
|||||||
|
|
||||||
export interface ISessionStoreOptions<S extends ISession> {
|
export interface ISessionStoreOptions<S extends ISession> {
|
||||||
readonly ttl: number
|
readonly ttl: number
|
||||||
readonly cleanup: number
|
readonly cleanupDelay: number
|
||||||
readonly getRepository: TRepositoryFactory<S>
|
readonly getRepository: TRepositoryFactory<S>
|
||||||
|
readonly logger: ILogger,
|
||||||
buildSession(sessionData: SessionData, session: ISession): S
|
buildSession(sessionData: SessionData, session: ISession): S
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,25 +26,27 @@ export type TRepositoryFactory<T> = () => Repository<T>
|
|||||||
export class SessionStore<S extends ISession> extends Store {
|
export class SessionStore<S extends ISession> extends Store {
|
||||||
|
|
||||||
protected readonly getRepository: TRepositoryFactory<S>
|
protected readonly getRepository: TRepositoryFactory<S>
|
||||||
protected readonly cleanup: (...args: never[]) => void
|
|
||||||
|
readonly cleanup = debounce(async () => {
|
||||||
|
try {
|
||||||
|
const now = Date.now()
|
||||||
|
// this method is debounced because is caused deadlock errors in tests.
|
||||||
|
// Be wary of future problems. Debounce should fix it but this still
|
||||||
|
// needs to be thorughly tested. The problem is a the delete statement
|
||||||
|
// which locks the whole table.
|
||||||
|
await this.getRepository().delete({
|
||||||
|
expiredAt: LessThan(now),
|
||||||
|
} as any)
|
||||||
|
} catch (err) {
|
||||||
|
this.options.logger.error('Error cleaning sessions: %s', err.stack)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly options: ISessionStoreOptions<S>,
|
protected readonly options: ISessionStoreOptions<S>,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.getRepository = options.getRepository
|
this.getRepository = options.getRepository
|
||||||
|
|
||||||
this.cleanup = debounce(async () => {
|
|
||||||
try {
|
|
||||||
const now = Date.now()
|
|
||||||
// FIXME causes deadlocks in tests
|
|
||||||
await this.getRepository().delete({
|
|
||||||
expiredAt: LessThan(now),
|
|
||||||
} as any)
|
|
||||||
} catch (err) {
|
|
||||||
console.log('error cleaning sessions', err)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async promiseToCallback<T>(
|
protected async promiseToCallback<T>(
|
||||||
@ -75,6 +79,8 @@ export class SessionStore<S extends ISession> extends Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set = (sid: string, session: SessionData, callback?: CallbackErr) => {
|
set = (sid: string, session: SessionData, callback?: CallbackErr) => {
|
||||||
|
this.cleanup.cancel()
|
||||||
|
|
||||||
const promise = Promise.resolve()
|
const promise = Promise.resolve()
|
||||||
.then(() => this.saveSession(
|
.then(() => this.saveSession(
|
||||||
this.options.buildSession(session, {
|
this.options.buildSession(session, {
|
||||||
|
|||||||
@ -38,10 +38,13 @@ export class TestUtils<T extends IRoutes> {
|
|||||||
|
|
||||||
let context: any
|
let context: any
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
connection = await database.connect()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
context = namespace.createContext();
|
context = namespace.createContext();
|
||||||
(namespace as any).enter(context)
|
(namespace as any).enter(context)
|
||||||
connection = await database.connect()
|
|
||||||
queryRunner = connection.createQueryRunner()
|
queryRunner = connection.createQueryRunner()
|
||||||
await queryRunner.connect()
|
await queryRunner.connect()
|
||||||
namespace.set(TRANSACTION_ID, shortid())
|
namespace.set(TRANSACTION_ID, shortid())
|
||||||
@ -56,10 +59,13 @@ export class TestUtils<T extends IRoutes> {
|
|||||||
}
|
}
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
namespace.set(TRANSACTION_ID, undefined)
|
namespace.set(TRANSACTION_ID, undefined)
|
||||||
namespace.set(ENTITY_MANAGER, undefined)
|
namespace.set(ENTITY_MANAGER, undefined);
|
||||||
await connection.close();
|
|
||||||
(namespace as any).exit(context)
|
(namespace as any).exit(context)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await database.close()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async createRole(name: string) {
|
async createRole(name: string) {
|
||||||
|
|||||||
@ -1,13 +1,21 @@
|
|||||||
export function debounce<A, R>(fn: (...args: A[]) => R, delay: number) {
|
export function debounce<A, R>(fn: (...args: A[]) => R, delay: number) {
|
||||||
let timeout: NodeJS.Timeout | null = null
|
let timeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
return function debounceImpl(...args: A[]): void {
|
const cancel = () => {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function debounceImpl(...args: A[]): void {
|
||||||
|
cancel()
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
fn(...args)
|
fn(...args)
|
||||||
}, delay)
|
}, delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debounceImpl.cancel = cancel
|
||||||
|
|
||||||
|
return debounceImpl
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user