Source: src/simulator/simulator-VirtualResource.js

import * as Util from '../util/util.js';
/**
 * @memberOf Simulator
 * @class VirtualResource
 * @classdesc 仮想リソース
 * 
 * @param {Object}
 *            system 仮想システム(シミュレーション中の現在時刻取得等に用いる)
 * @param {String}
 *            [name = "unlimited"] リソース名("unlimited"はリソース解放待ちを管理しない)
 * @param {Number}
 *            [holdCapacity = 1.0] 保有リソース総量(数)
 * @param {Number}
 *            [timeout = 10秒] 処理のタイムアウト時間(未使用)
 * @param {Number}
 *            [waitCapacity = Number.MAX_SAFE_INTEGER]
 *            リソース取得待ちキューの深さ(数)、キュー溢れ時は即時エラー終了しリソース処理しない
 * @param {Number}
 *            [queueWait = 10秒] 最大キュー滞留時間(リソース取得待ちタイムアウト時間)
 * @param {Boolean}
 *            [log=false] 詳細ログ出力有無
 */
export default (function() { // #53
    /** @constructor */
    function VirtualResource(system, name,
                        holdCapacity, timeout, waitCapacity, queueWait, log){
        if(!(this instanceof VirtualResource)){
            return new VirtualResource(system, name,
                        holdCapacity, timeout, waitCapacity, queueWait, log);
        }
        this._system = system;
        this._name = name || "unlimited";
        this._log = log ? +log : 0; // #59

        // 処理待ち管理用
        this._waitTimeout = (typeof(queueWait) !== "undefined")
                                ? queueWait : 10000;   // キュー滞留時間上限
        this._waitCapacity  = (typeof(waitCapacity) !== "undefined")
                                ? waitCapacity : Number.MAX_SAFE_INTEGER; // キューの深さ
        this._waitHeap = Util.Heap(    // リソース解放待ちキュー(登録時間順)
                function(obj){ return obj.getTime(); });
        
        // リソース管理用
        this._holdTimeout  = (typeof(timeout)  !== "undefined") 
                                ? timeout : 10000;   // 処理のタイムアウト時間
        this._holdCapacity = (typeof(holdCapacity) !== "undefined")
                                ? holdCapacity : 1.0;   // 保有リソース量(数)
        this._holdingQty = 0;   // 使用リソース量
        this._holdHeap = Util.Heap( // 処理のタイムアウト管理用ヒープ{obj:,val:} #59
                 function(node){ return node.val; }, // valはタイムアウト時刻
                 function(node){ return node.obj; }); // objはvApp
        
        // イベントスケジュール制御用
        this._sequenceTime = 0;   // シミュレータに登録したイベントの時刻(タイムアウトチェック用)
        this._isScheduled = false; // シミュレータにタイムアウトチェックイベントをスケジュールしたか
        if (0 < this._waitTimeout && 0 < this._holdTimeout){ // スケジュール間隔 #61
            this._interval = Math.min(this._waitTimeout, this._holdTimeout);
        } else if (0 < this._waitTimeout) {
            this._interval = this._waitTimeout;
        } else if (0 < this._holdTimeout) {
            this._interval = this._holdTimeout;
        } else {
            this._interval = 0;
        }
    }

    /** @private */
    //

    // public
    /**
     * リソースチェックイベント(タイムアウトチェック)を開始する
     * 
     * @memberof Simulator.VirtualResource
     * @param {Number}
     *            startTime 開始時刻(UNIX時刻:ミリ秒)
     * @param {Object}
     *            system VirtualSystem
     * @return {Object} イベント(this)
     */
    VirtualResource.prototype.start = function(startTime, system) { // #59
        this._sequenceTime = startTime + this._interval; // シミュレータに登録するイベントの時刻
        system.setEvent(this);    // シミュレータにタイムアウトチェックイベントをスケジュールする
        this._isScheduled = true; // 「シミュレータにタイムアウトチェックイベントをスケジュールしたかフラグ」をON
        return this;
    };
    
    /**
     * イベント時刻を返却する
     * 
     * @memberof Simulator.VirtualResource
     * @return {Number} イベント時刻(UNIX時刻:ミリ秒)
     */
    VirtualResource.prototype.getTime = function() {
        return this._sequenceTime;
    };
    
    /**
     * タイムアウトチェック用仮想イベント
     * 
     * @memberof Simulator.VirtualResource
     * @param {Object}
     *            system VirtualSystem
     * @return {Array}再スケジュールするイベント(仮想アプリケーションorリソース)の配列、登録処理完了時はthisを含まない
     */
    VirtualResource.prototype.next = function(system) {
        var events = []; // 戻り値
        var now = this.getTime();
        // リソース解放待ち時間がタイムアウトしたappをタイムアウトさせる
        var queuedTime = Number.MIN_SAFE_INTEGER; 
        while (0 < this._waitHeap.size() && this._waitTimeout <= now - queuedTime) {
            queuedTime = this._waitHeap.top().getTime();
            if (this._waitTimeout <= now - queuedTime) { // キューイング取引がタイムアウトしているとき
                // リソース解放待ちHeapからfreeするappを取り出す
                var app = this._waitHeap.pop();
                // appをアベンドさせる(holdリソース解放なし)
                var apps = app.abend(system, this, "E_QTO", this._name + " queue timeout",
                        false, queuedTime + this._waitTimeout); // appにfree時刻をセットする
                // appsをスケジュールイベント登録対象に加える
                if (apps.length){
                    events = events.concat(apps);
                }
            }
        }
        // リソース使用時間がタイムアウトしたappをタイムアウトさせる #59
        var holdTimeoutTime = Number.MIN_SAFE_INTEGER;
        while (0 < this._holdHeap.size() && holdTimeoutTime <= now) {
            var top = this._holdHeap.top();
            holdTimeoutTime = top.val;
            if (holdTimeoutTime <= now) { // 処理中取引がタイムアウトしているとき #61
                var app = top.obj;
                // appがスケジュールされている場合削除する(スケジューラに登録されていない場合何も起きない)
                system.removeEvent(app);
                // appの使用時間がタイムアウトしたリソースを解放する(注:abendで解放させると永久ループする)
                events = events.concat(this.free(app));                
                // appをタイムアウト時刻にアベンドさせる(holdリソース解放を伴う)
                events = events.concat(app.abend(system, this,"E_HTO", 
                            this._name + " hold timeout", false, holdTimeoutTime));
            }
        }
        // 次回タイムアウトチェック時刻を設定する
        if ((0 < this._waitHeap.size()) || (0 < this._holdHeap.size())) {
            // タイムアウトの設定があるとき、 #61
            // リソース解放待ちvAppがあるとき、(タイムアウトしていない)最古vAppのタイムアウト時刻
            // リソース解放待ちvAppがないとき、現在からタイムアウト時刻後 にスケジュールする
            var nextWaitTimeout = Number.MAX_SAFE_INTEGER;
            if (0 < this._waitTimeout) {
                if ( 0 < this._waitHeap.size() 
                        && now <= this._waitTimeout + this._waitHeap.top().getTime()) {
                    nextWaitTimeout = this._waitHeap.top().getTime() + this._waitTimeout; 
                } else {
                    nextWaitTimeout = now + this._waitTimeout;
                }
            }            
            var nextHoldTimeout = Number.MAX_SAFE_INTEGER;
            if (0 < this._holdTimeout) {
                if(0 < this._holdHeap.size()) {
                    nextHoldTimeout = this._holdHeap.top().val;
                } else {
                    nextHoldTimeout = now + this._holdTimeout;
                }
            }
            this._sequenceTime = Math.min(nextWaitTimeout, nextHoldTimeout);
            if (this._sequenceTime < Number.MAX_SAFE_INTEGER) {
                events.push(this); // タイムアウトチェックイベントをケジュールイベント登録対象に加える
            } else {
                this._isScheduled = false;
            }
        } else { 
            // 以外のとき、シミュレータにタイムアウトチェックイベントを再スケジュールしない
            this._isScheduled = false;
        }
        return events;
    };

    /**
     * リソースを取得する
     * 
     * @memberof Simulator.VirtualResource
     * @param {Object}
     *            system VirtualSystem
     * @param {Object}
     *            vApp リソースにhold要求する仮想AP
     * @return {Objcet} 処理結果{result:boolean, events:Array}<br>
     *         {boolean} result :
     *         true:正常(リソース取得、取得待ち、取得不要)、false:エラー(リソース枯渇E_QOF))<br>
     *         {Array} :events リソース取得後、スケジューラに登録するイベントの配列<br>
     *         [vApp]: リソースを取得できたとき、もしくはリソース枯渇時でvApp再処理の場合、スケジュール対象の vApp
     *         が登録された配列[vApp]を返却<br>
     *         []: リソース待ちに登録されたとき、既にリソースが管理するスケジューラに登録さているので、空の配列[]を返却<br>
     *         もしくはリソース枯渇時で繰返し完了時、再スケジュールしないので[]を返却
     */
    VirtualResource.prototype.hold = function(system, vApp) {
        var ret = { result : true,
                    events : [vApp] }; // 戻り値 #61
        if (this._name === "unlimited") return ret; // [vApp] リソース解放待ちを管理しないとき
        vApp.logger(4, this._log, vApp._sequenceTime, vApp, this, 'HOLD' , undefined);
        // タイムアウトチェックイベントがスケジュールされていないとき、スケジュールする
        if (!this._isScheduled && (0 < this._interval)) {
            this.start(system.getTime(), system);
        }
        // リソースを取得できるとき、使用リソース量(数)を増やし、実行中処理管理ヒープに登録し、スケジュール対象とする
        var amount = vApp.getAmount(this); // 消費リソース量(デフォルト1.0)
        if (amount <= (this._holdCapacity - this._holdingQty)) {
            // 使用リソースを増やす
            this._holdingQty += amount;
            // タイムアウト管理対象リソースのとき、vAppをタイムアウト管理対象に加える #59
            if (0 < this._holdTimeout) {
                this._holdHeap.push({obj: vApp, val: system.getTime() + this._holdTimeout});
            }
            vApp.logger(3, this._log, system.getTime(), vApp, this, 'hold' , undefined);
            return ret; // [vApp]
        }

        // リソース解放待ちキューに空きがあるとき、vAppをリソース解放待ちに 登録する(スケジュールしない)
        if ((this._waitHeap.size() < this._waitCapacity) && (0 < this._waitTimeout)){
            // リソース解放待ちタイムアウト管理対象に加える
            this._waitHeap.push(vApp);
            vApp.addHistory("wait:" + this._name, system.getTime()); // #61
            vApp.logger(3, this._log, system.getTime(), vApp, this, 'wait' , undefined);
            return { result: true, events: [] };
        }
        
        // リソース解放待ちキューが溢れていた時、リソースを取得できずにアベンド(リソース解放なし、自AP継続の場合[vApp]をリターン) #61
        var apps = vApp.abend(system, this, "E_QOF",
                    "[" + this._name + "] over flow", false);
        vApp.logger(3, this._log, system.getTime(), vApp, this, 'over' , undefined);
        return { result: false, events: apps }; // #61
    };

    /**
     * 引数vAppが使用していたリソースを解放する
     * 
     * @memberof Simulator.VirtualResource
     * @param {Object}
     *            vApp リソースにfree要求する仮想AP
     * @param {Boolean}
     *            [isHolding=true] 該当vAppが自リソースをholdしているか否か<br>
     *            false指定時、指定vAppはリソースをholdしていない前提で、hold vApp一覧からの削除処理を行わない
     * @return {Array} スケジューラに登録するイベントの配列([vApp] | [])
     */
    VirtualResource.prototype.free = function(vApp, isHolding) { // #59
        vApp.logger(4, this._log, this._system.getTime(), vApp, this, 'FREE' , undefined);
        isHolding = (typeof(isHolding) === "boolean") ? isHolding : true;
        if (this._name === "unlimited") return []; // リソース解放待ちを管理しないとき
        var vApps = []; // 戻り値
        // 自リソースを使用している可能性があるとき、使用リソースを解放する
        // 解放したvAppが使用していたリソース量(デフォルト1.0)を、使用リソース量(数)から減らす #59
        if (isHolding === true) { // #61
            this._holdingQty -= vApp.getAmount(this);
            // タイムアウト管理対象リソースのとき、vAppをタイムアウト管理対象から削除する #61
            if (0 < this._holdTimeout) { // #61
                var app = this._holdHeap.del(vApp);
            }
            vApp.addHistory("free:" + this._name, this._system.getTime());
            vApp.logger(3, this._log, this._system.getTime(), vApp, this, 'del' , undefined);
        }
        // リソース解放待ちキューから、空きリソースで処理できるようになったvAppを取り出しスケジュールする #61
        var marginQty = this._holdCapacity - this._holdingQty;
        var addQty = this._waitHeap.top()
                    ? this._waitHeap.top().getAmount(this) // 次のリソース解放待ちキューの使用量
                    : Number.MAX_SAFE_INTEGER;
        for (var i = this._waitHeap.size() && addQty <= marginQty; 0 < i; i--) {
            // リソース解放待ちキューからfreeするappを取り出す
            var app = this._waitHeap.pop();
            // appにfree時刻をセットし、スケジュールイベント登録対象に加える
            // (注:リソース取得はスケジュール後、E_HTOに伴う他のリソースの時刻は将来)
            app.setSequenceTime(this._system.getTime(), "release:" + this._name);
            vApps.push(app);
            // 次のappのリソース量を加える
            addQty += this._waitHeap.top() 
                    ? this._waitHeap.top().getAmount(this) // 次のリソース解放待ちキューの使用量
                    : Number.MAX_SAFE_INTEGER;
        }
        return vApps;
    };


    /**
     * 引数vAppをリソース開放待ちキューからリリースする
     * 
     * @memberof Simulator.VirtualResource
     * @param {Object}
     *            vApp リリースする仮想AP
     * @return {Object | undefined} リリースした仮想AP
     */
    VirtualResource.prototype.release = function(vApp) { // #61
        var app = this._waitHeap.del(vApp);
        if (app) vApp.addHistory("release", this._system.getTime());
        return app;
    }

    
    
    /**
     * イベント終了時処理(リソースが管理しているvAppをfinishさせる(強制終了させ処理中vAppはTATログ出力する)
     * 
     * @memberof Simulator.VirtualResource
     * @param {Object}
     *            system VirtualSystem
     * @param {String}
     *            [logID="N_000"] ログID(ログメッセージの先頭文字)
     * @param {String}
     *            [logMesseage=""] ログメッセージ
     * @param {Number}
     *            forceTime 強制終了時刻を指定する(ミシュレーション停止後のeTat強制出力用)
     * @return {null}
     */
    VirtualResource.prototype._finish = function(system, logID,
                                        logMesseage, forceTime) { // #59
        // シミュレーション終了後処理(処理中のvAppを強制終了する)
        logID = logID || "N_EoS";
        logMesseage = logMesseage || "";
        while(0 < this._waitHeap.size()){
            event = this._waitHeap.pop();
            if (event._finish) {
                event._finish(system, logID,
                        "[" + this._name + "] " + logMesseage , forceTime);
            }
        }
    }

    /* new */
    return VirtualResource;
}());