Skip to content

Commit

Permalink
add VolatileMap for sid caches
Browse files Browse the repository at this point in the history
  • Loading branch information
benStre committed Dec 11, 2023
1 parent f98ba34 commit ea8a236
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 16 deletions.
44 changes: 29 additions & 15 deletions compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { MessageLogger } from "../utils/message_logger.ts";
import { JSTransferableFunction } from "../types/js-function.ts";
import { client_type } from "../utils/constants.ts";
import { normalizePath } from "../utils/normalize-path.ts";
import { VolatileMap } from "../utils/volatile-map.ts";

await wasm_init();
wasm_init_runtime();
Expand Down Expand Up @@ -326,17 +327,17 @@ export class Compiler {
return tmp.buffer;
}

private static sid_return_indices:Map<number,number> = new Map();
private static sid_incs:Map<number,number> = new Map();
private static sid_incs_remote:Map<Target, Map<number,number>> = new Map();
private static sid_return_indices = new VolatileMap<number,number>();
private static sid_incs = new VolatileMap<number,number>();
private static sid_incs_remote:Map<Target, VolatileMap<number,number>> = new Map();

public static readonly MAX_SID = 4_294_967_295;
public static readonly MAX_BLOCK = 65_535;

public static readonly MAX_DXB_BLOCK_SIZE = Compiler.MAX_UINT_16; // default max block size (Infinity)

/** create a new random SID */
public static generateSID():number{
public static generateSID(keepalive = false):number{
let sid:number;
// get unique SID
do {
Expand All @@ -345,17 +346,19 @@ export class Compiler {

this.sid_return_indices.set(sid,0);
this.sid_incs.set(sid,0);

// TODO: alternative solution? at some point, the map max size will be reached
if (keepalive) {
this.sid_return_indices.keepalive(sid, Infinity)
this.sid_incs.keepalive(sid, Infinity)
}
return sid;
}
/** delete an SID */
private static removeSID(sid:number){
this.sid_return_indices.delete(sid);
this.sid_incs.delete(sid);
}

/** get return index ++ for a specific SID */
public static getNextReturnIndexForSID(sid:number):number {
if (!this.sid_return_indices.has(sid)) {this.sid_return_indices.set(sid,0);this.sid_incs.set(sid,0);} // sid not yet loaded?
let c = <number>this.sid_return_indices.get(sid);
let c = <number>this.sid_return_indices.keepalive(sid);
if (c > this.MAX_BLOCK) c = 0;
this.sid_return_indices.set(sid, c+1);
return c;
Expand All @@ -364,7 +367,7 @@ export class Compiler {
private static getBlockInc(sid:number):number {
if (!this.sid_return_indices.has(sid)) {this.sid_return_indices.set(sid,0);this.sid_incs.set(sid,0);} // sid not yet loaded?

let c = <number>this.sid_incs.get(sid);
let c = <number>this.sid_incs.keepalive(sid);
if (c > this.MAX_BLOCK) c = 0;
this.sid_incs.set(sid, c+1);
return c;
Expand All @@ -373,16 +376,16 @@ export class Compiler {
// count up inc individually for different remote receivers (important for RESPONSE dxb)
private static getBlockIncForRemoteSID(sid: number, remote_endpoint:Endpoint, reset_inc = false) {
if (!(remote_endpoint instanceof Target)) throw new CompilerError("Can only send datex responses to endpoint targets");
if (!this.sid_incs_remote.has(remote_endpoint)) this.sid_incs_remote.set(remote_endpoint, new Map());
if (!this.sid_incs_remote.has(remote_endpoint)) this.sid_incs_remote.set(remote_endpoint, new VolatileMap());

const sid_incs = this.sid_incs_remote.get(remote_endpoint)!;

if (!sid_incs.has(sid)) {
if (reset_inc) return 0; // don't even bother toc create a 0-entry, just return 0 directly
if (reset_inc) return 0; // don't even bother to create a 0-entry, just return 0 directly
sid_incs.set(sid, 0); // sid not yet loaded?
}

let c = sid_incs.get(sid)!;
let c = sid_incs.keepalive(sid)!;
if (c > this.MAX_BLOCK) c = 0;
sid_incs.set(sid, c+1);
//logger.warn("INC for remote SID " + sid, c, (reset_inc?'RESET':''));
Expand Down Expand Up @@ -5913,4 +5916,15 @@ export const FILE_TYPE = {
JSON: ["application/json", "json"]
} as const;

export type DATEX_FILE_TYPE = typeof FILE_TYPE[keyof typeof FILE_TYPE];
export type DATEX_FILE_TYPE = typeof FILE_TYPE[keyof typeof FILE_TYPE];


// debug:
setInterval(()=> {
console.log(
"SID cache sizes (VolatileMap): ",
Compiler.sid_return_indices.size,
Compiler.sid_incs.size,
Compiler.sid_incs_remote.size
)
}, 1000*60*30 /*30min*/)
2 changes: 1 addition & 1 deletion types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export class Function<T extends (...args: any) => any = (...args: any) => any> e
private setRemoteEndpoint(endpoint: Endpoint){
//let my_pointer = <Pointer> Pointer.pointerifyValue(this);

const sid = Compiler.generateSID(); // fixed sid to keep order
const sid = Compiler.generateSID(true); // fixed sid to keep order

// save pointer in variable:
/**
Expand Down
92 changes: 92 additions & 0 deletions utils/volatile-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* A map that automatically removes entries after a defined time period
* or when the map size limit is reached
*/
export class VolatileMap<K,V> extends Map<K,V> {

static readonly MAX_MAP_ENTRIES = 2**24
static readonly MAP_ENTRIES_CLEANUP_CHUNK = 1000

#options: VolatileMapOptions
#timeouts = new Map<K, number>()

constructor(iterable?: Iterable<readonly [K, V]> | null, options: Partial<VolatileMapOptions> = {}) {
super(iterable)
this.#options = options as VolatileMapOptions;
this.#options.entryLifetime ??= 30*60 // 30min
this.#options.preventMapOverflow ??= true
}

/**
* Resets the lifetime of the entry.
* @param key
* @param overrideLifetime optionally specify a lifetime for this entry that differs from options.entryLifetime
* @returns the current value for the key
*/
keepalive(key: K, overrideLifetime?: number) {
this.#setTimeout(key, overrideLifetime);
return this.get(key)
}

set(key: K, value: V): this {
// create new lifetime timeout
this.#setTimeout(key)
// maximum map size reached?
if (this.#options.preventMapOverflow && this.size >= VolatileMap.MAX_MAP_ENTRIES) {
console.log("VolatileMap size limit ("+VolatileMap.MAX_MAP_ENTRIES+") reached. Force removing "+VolatileMap.MAP_ENTRIES_CLEANUP_CHUNK+" entries.")
let i = 0;
for (const key of this.keys()) {
if (i == VolatileMap.MAP_ENTRIES_CLEANUP_CHUNK) break;
this.delete(key)
i++;
}
}
return super.set(key, value)
}

delete(key: K): boolean {
this.#clearTimeout(key);
return super.delete(key)
}

clear(): void {
for (const key of this.keys()) this.#clearTimeout(key);
return super.clear();
}


#clearTimeout(key: K) {
if (this.#timeouts.has(key)) {
clearTimeout(this.#timeouts.get(key));
this.#timeouts.delete(key);
}
}

#setTimeout(key: K, overrideLifetime?: number) {
// reset previous timeout
this.#clearTimeout(key);
const lifetime = overrideLifetime ?? this.#options.entryLifetime;
if (Number.isFinite(lifetime)) {
this.#timeouts.set(key, setTimeout(() => {
this.delete(key)
}, lifetime * 1000))
}
}
}

export type VolatileMapOptions = {
/**
* Entry lifetime in seconds. If set to Infinity,
* entries are only automatically removed if the map size limit
* is reached and preventMapOverflow is set to true.
* Default value: 1800 (30min)
*/
entryLifetime: number,
/**
* Automatically deletes the oldest entries if the number of map entries
* exceeds the maximum allowed number (2^24), even if their lifetime
* is not yet expired.
* Default value: true
*/
preventMapOverflow: boolean
}

0 comments on commit ea8a236

Please sign in to comment.