[Typescript] Lock - Code snippet.

Language/Typescript 2018.06.26 06:05
Typescript code to implement simple lock... 시험 환경: NodeJS: 8.X.X Typescript: 2.7.X lock.ts
import * as logger from '../logger';
import * as cls from 'cls-hooked';

class Settler {
    constructor(
        public resolve: any,
        public reject: any,
        public owner: string,
        public lock: Lock
    ) {}
}

const CLS_NS = '../XXX/lock';
const CLS_KEY_LOCK = 'lock';
const clsns = cls.createNamespace(CLS_NS);

const TEMP_OWNER_PREFIX = '$#@#$+|_)';
let tempOwnerInc = 0;
function getTempOwner(): string {
    return TEMP_OWNER_PREFIX + tempOwnerInc++;
}

/**
 * Class instance method decorator.
 * Return type of decoratee(method) MUST be 'Promise'!
 * NOTE that, this decorator change return type of method to Promise<any>!
 *
 * @param lockProperty name of 'Lock' property name to use.
 */
export function clsLocked(lockProperty: string): any {
    return function (
        target: any, key: string | symbol, descriptor: TypedPropertyDescriptor<any>
    ): TypedPropertyDescriptor<any> | void {
        if (descriptor === undefined) {
            descriptor = Object.getOwnPropertyDescriptor(target, key)!;
        }
        let orig = descriptor.value;
        descriptor.value = async function (): Promise<any> {
            let instance = <any>this;
            let funcArgs = arguments;
            let lockobj = <Lock>instance[lockProperty];
            log.assert(!!lockobj);
            let clsLockSet = <Set<Lock>>clsns.get(CLS_KEY_LOCK);
            if (clsLockSet) {
                // in cls lock context
                if (clsLockSet.has(lockobj)) {
                    log.assert(lockobj.getOwner() === clsns);
                     return await orig!.apply(instance, funcArgs);
                } else {
                     clsLockSet.add(lockobj);
                    return lockobj.run(clsns, async () => {
                        return await orig!.apply(instance, funcArgs);
                    });
                }
            } else {
                // Start with new context lock.
                return clsns.runAndReturn(async () => {
                    clsns.set(CLS_KEY_LOCK, new Set<Lock>([lockobj]));
                    return lockobj.run(clsns, async () => {
                        return await orig!.apply(instance, funcArgs);
                    });
                });
            }
        };
        return descriptor;
    };
}


//////////////////////////////////////////////////////////////////////////////
//
//
//
//////////////////////////////////////////////////////////////////////////////

export async function runClsLocked(
    lock: Lock, f: (...args: any[]) => Promise<any>, ...args: any[]
): Promise<any> {
    return clsns.runAndReturn(async () => {
        clsns.set(CLS_KEY_LOCK, new Set<Lock>([lock]));
        return lock.run(clsns, async () => {
            return await f(...args);
        });
    });
}

// Interface of Lock
//
// - await lock.get()
// - await lock.put()
// - await lock.run(owner, func, ...)
// [TODO]
// - Implement Read/Write Lock
//
// - await lock.try()
// - await lock.get(timeout)
// - await lock.run(timeout, owner, func, ...)
export class Lock {
    private owner: any = undefined;
    private waitq: Settler[] = [];
    constructor(
        public readonly name: string // Usually, this is used for debugging
    ) {}

    getOwner(): any {
        return this.owner;
    }

    waitsz(): number {
        return this.waitq.length;
    }

    /**
     * DO NOT USE 'get/put' combination. Use 'run' if possible.
     */
    async get(owner: string): Promise<Lock> {
        if (this.owner) {
            return new Promise<Lock>((res, rej) => {
                this.waitq.push(new Settler(res, rej, owner, this));
            });
        } else {
            this.owner = owner;
            return this;
        }
    }

    /**
     * DO NOT USE 'get/put' combination. Use 'run' if possible.
     *
     * @returns size of remaining wait-queue.
     */
    put(owner: string): number {
        log.assertFatal(owner === this.owner);
        let settler = this.waitq.shift();
        if (undefined === settler) {
            this.owner = undefined;
            return 0;
        }
        this.owner = settler.owner;
        settler.resolve(this);
        return this.waitq.length;
    }

    async run(owner: any, func: (...args: any[]) => any, ...args: any[]): Promise<any> {
        owner = owner ? owner : getTempOwner();
        await this.get(owner);
        try {
            return await func(...args);
        } finally {
            this.put(owner);
        }
    }
}

Simple example used with decorator.
class Foo {
    private lock: Lock;
    constructor() {
        this.lock = new Lock('Foo');
    }
    private priv0(v: number, tm: number): Promise<number> {
        return new Promise((res, rej) => {
            setTimeout(() => {
                res(v);
            }, tm);
        });
    }

    @clsLocked('lock')
    async pub00(v: number, tm: number): Promise<number> {
        return await this.priv0(v, tm);
    }

    @clsLocked('lock')
    async pub01(v: number, tm: number): Promise<number> {
        return await this.pub00(v, tm);
    }
}

일단 어느정도 동작은 하는것 같은데, 엄밀한 검증까지는... 즉 아직 실제 production server에 사용된 적은 없다...
Trackback 0 : Comment 0