gemini-core
Version:
Utility which contains common modules for gemini and hermione
129 lines (103 loc) • 4.19 kB
JavaScript
'use strict';
const _ = require('lodash');
const Promise = require('bluebird');
const yallist = require('yallist');
const Pool = require('./pool');
const CancelledError = require('../errors/cancelled-error');
const debug = require('debug');
const {buildCompositeBrowserId} = require('./utils');
module.exports = class LimitedPool extends Pool {
static create(underlyingPool, opts) {
return new LimitedPool(underlyingPool, opts);
}
/**
* @extends BasicPool
* @param {Number} limit
* @param {BasicPool} underlyingPool
*/
constructor(underlyingPool, opts) {
super();
this.log = debug(`${opts.logNamespace}:pool:limited`);
this.underlyingPool = underlyingPool;
this._limit = opts.limit;
this._launched = 0;
this._requests = 0;
this._requestQueue = yallist.create();
this._highPriorityRequestQueue = yallist.create();
this._isSpecificBrowserLimiter = _.isBoolean(opts.isSpecificBrowserLimiter)
? opts.isSpecificBrowserLimiter
: true;
}
getBrowser(id, opts = {}) {
const optsToPrint = JSON.stringify(opts);
this.log(`get browser ${id} with opts:${optsToPrint} (launched ${this._launched}, limit ${this._limit})`);
++this._requests;
return this._getBrowser(id, opts)
.catch((e) => {
--this._requests;
return Promise.reject(e);
});
}
freeBrowser(browser, opts = {}) {
--this._requests;
const nextRequest = this._lookAtNextRequest();
const compositeIdForNextRequest = nextRequest && buildCompositeBrowserId(nextRequest.id, nextRequest.version);
const hasFreeSlots = this._launched < this._limit;
const shouldFreeUnusedResource = this._isSpecificBrowserLimiter && this._launched > this._requests;
const force = opts.force || shouldFreeUnusedResource;
const optsForFree = {force, compositeIdForNextRequest, hasFreeSlots};
this.log(`free browser ${browser.fullId} with opts:${JSON.stringify(optsForFree)}`);
return this.underlyingPool
.freeBrowser(browser, optsForFree)
.finally(() => this._launchNextBrowser());
}
cancel() {
this.log('cancel');
const reject_ = (entry) => entry.reject(new CancelledError());
this._highPriorityRequestQueue.forEach(reject_);
this._requestQueue.forEach(reject_);
this._highPriorityRequestQueue = yallist.create();
this._requestQueue = yallist.create();
this.underlyingPool.cancel();
}
_getBrowser(id, opts = {}) {
if (this._launched < this._limit) {
this.log('can launch one more');
this._launched++;
return this._newBrowser(id, opts);
}
this.log('queuing the request');
const queue = opts.highPriority ? this._highPriorityRequestQueue : this._requestQueue;
const {version} = opts;
return new Promise((resolve, reject) => {
queue.push({id, version, resolve, reject});
});
}
/**
* @param {String} id
* @returns {Promise<Browser>}
*/
_newBrowser(id, opts) {
this.log(`launching new browser ${id} with opts:${JSON.stringify(opts)}`);
return this.underlyingPool.getBrowser(id, opts)
.catch((e) => {
this._launchNextBrowser();
return Promise.reject(e);
});
}
_lookAtNextRequest() {
return this._highPriorityRequestQueue.get(0) || this._requestQueue.get(0);
}
_launchNextBrowser() {
const queued = this._highPriorityRequestQueue.shift() || this._requestQueue.shift();
if (queued) {
const compositeId = buildCompositeBrowserId(queued.id, queued.version);
this.log(`has queued requests for ${compositeId}`);
this.log(`remaining queue length: ${this._requestQueue.length}`);
this._newBrowser(queued.id, {version: queued.version})
.then(queued.resolve, queued.reject);
} else {
this._launched--;
}
}
};