Skip to content

Commit

Permalink
fix(ContextStore): auto eliminate context store to avoid high memory …
Browse files Browse the repository at this point in the history
…consumsion (#19)
  • Loading branch information
winguse committed Apr 10, 2018
1 parent dbc512f commit 42700cd
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

116 changes: 113 additions & 3 deletions src/envoy-context-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,109 @@ export class NodeInfo {
}
}

const store = new Map<number, NodeInfo>();
/**
* Eliminate Store
* using two map for context storage, to avoid holding too many data
* it will eliminate the old data
*/
export class EliminateStore {
private old = new Map<number, NodeInfo>();
private current = new Map<number, NodeInfo>();

private lastEliminateTime = Date.now();

/**
* get context
* @param asyncId asyncId
*/
get(asyncId: number) {
const infoFromCurrent = this.current.get(asyncId);
if (infoFromCurrent !== undefined) {
return infoFromCurrent;
}
const infoFromOld = this.old.get(asyncId);
if (infoFromOld !== undefined) {
this.current.set(asyncId, infoFromOld);
}
return infoFromOld;
}

/**
* set context
* @param asyncId asyncId
* @param info context info
*/
set(asyncId: number, info: NodeInfo) {
this.current.set(asyncId, info);
}

/**
* delete context
* @param asyncId asyncId
*/
delete(asyncId: number) {
this.current.delete(asyncId);
this.old.delete(asyncId);
}

/**
* clear all data
*/
clear() {
this.old.clear();
this.current.clear();
}

/**
* eliminate the old data
*/
eliminate() {
this.old = this.current;
this.current = new Map<number, NodeInfo>();
this.lastEliminateTime = Date.now();
}

/**
* get last eliminate time
*/
getLastEliminateTime() {
return this.lastEliminateTime;
}

/**
* the current size
*/
size() {
return this.current.size;
}

/**
* the old store size
*/
oldSize() {
return this.old.size;
}
}

const store = new EliminateStore();
let enabled = false;
let eliminateInterval = 300 * 1000; // 300s, 5 mins

/**
* set the store's eliminate interval, context data older than this and not
* read will be eventually eliminated
* @param interval time in milliseconds
*/
function setEliminateInterval(interval: number) {
eliminateInterval = interval;
}

/**
* get eliminate interval
*/
function getEliminateInterval() {
return eliminateInterval;
}

/**
* clean up will decrease the reference count.
Expand All @@ -53,6 +154,10 @@ function storeCleanUp(asyncId: number) {

const asyncHook = asyncHooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
/* istanbul ignore next */
if (Date.now() - store.getLastEliminateTime() > eliminateInterval) {
store.eliminate();
}
let triggerInfo = store.get(triggerAsyncId);
if (!triggerInfo) {
triggerInfo = new NodeInfo(-1);
Expand Down Expand Up @@ -122,6 +227,7 @@ function set(context: EnvoyContext) {
}
const asyncId = asyncHooks.executionAsyncId();
const info = store.get(asyncId);
/* istanbul ignore next */
if (info === undefined) {
console.trace(
"[envoy-node] Cannot find info of current execution, have you enabled the context store correctly?"
Expand All @@ -138,6 +244,7 @@ function set(context: EnvoyContext) {
*/
function getContext(asyncId: number): EnvoyContext | undefined {
const info = store.get(asyncId);
/* istanbul ignore next */
if (!info) {
return undefined;
}
Expand All @@ -157,6 +264,7 @@ function get(): EnvoyContext | undefined {
}
const asyncId = asyncHooks.executionAsyncId();
const context = getContext(asyncId);
/* istanbul ignore next */
if (context === undefined) {
console.trace(
"[envoy-node] Cannot find info of current execution, have you enabled and set the context store correctly?"
Expand All @@ -169,7 +277,7 @@ function isEnabled() {
return enabled;
}

function getStore() {
function getStoreImpl() {
return store;
}

Expand All @@ -179,5 +287,7 @@ export default {
set,
get,
isEnabled,
getStore
getStoreImpl,
getEliminateInterval,
setEliminateInterval
};
1 change: 1 addition & 0 deletions src/envoy-proto-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export default function envoyProtoDecorator<T extends EnvoyClient>(
}

const { originalName }: { originalName?: string } = method;
/* istanbul ignore next */
if (originalName) {
// should alway have
prototype[originalName] = prototype[name];
Expand Down
49 changes: 48 additions & 1 deletion test/envoy-context-store.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EnvoyContext from "../src/envoy-context";
import store from "../src/envoy-context-store";
import store, { EliminateStore, NodeInfo } from "../src/envoy-context-store";
import { sleep } from "./lib/utils";

class Data {
Expand Down Expand Up @@ -66,6 +66,8 @@ describe("Envoy context store", () => {
expect(data.inputCtx).toBe(data.fromPromise);
expect(data.inputCtx).toBe(data.fromSetTimeout);
});
expect(store.getStoreImpl().size()).toBe(0);
expect(store.getStoreImpl().oldSize()).toBe(0);
});

it("should trace error when set context if store is not enabled", () => {
Expand Down Expand Up @@ -96,4 +98,49 @@ describe("Envoy context store", () => {
expect(console.trace).toBeCalled();
console.trace = originalTrace;
});

it("Eliminate Store should works", () => {
const es = new EliminateStore();
expect(es.get(1)).toBeUndefined();
expect(es.size()).toBe(0);
expect(es.oldSize()).toBe(0);

const info = new NodeInfo(1);
es.set(1, info);
expect(es.get(1)).toBe(info);
expect(es.size()).toBe(1);
expect(es.oldSize()).toBe(0);

const oldTime = es.getLastEliminateTime();
es.eliminate();
expect(es.size()).toBe(0);
expect(es.oldSize()).toBe(1);
expect(es.getLastEliminateTime()).toBeGreaterThan(oldTime);

const info2 = new NodeInfo(2);
es.set(2, info2);
expect(es.get(2)).toBe(info2);
expect(es.size()).toBe(1);
expect(es.oldSize()).toBe(1);

expect(es.get(1)).toBe(info);
expect(es.size()).toBe(2);
expect(es.oldSize()).toBe(1);

es.delete(1);
expect(es.size()).toBe(1);
expect(es.oldSize()).toBe(0);
expect(es.get(1)).toBeUndefined();
expect(es.get(2)).toBe(info2);

es.clear();
expect(es.size()).toBe(0);
expect(es.oldSize()).toBe(0);
});

it("should set eliminate interval works", () => {
expect(store.getEliminateInterval()).toBe(300 * 1000);
store.setEliminateInterval(1);
expect(store.getEliminateInterval()).toBe(1);
});
});

0 comments on commit 42700cd

Please sign in to comment.