all files / src/ pool.js

72.58% Statements 45/62
54.55% Branches 12/22
90.91% Functions 10/11
74.07% Lines 40/54
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123                                                                                                                                               10×                        
const WebWorkerPromise = require('./index');
 
class WorkerPool {
  constructor({create, maxThreads, terminateAfterDelay, maxConcurrentPerWorker}) {
    this._queue = [];
    this._workers = [];
    this._createWorker = create;
    this._maxThreads = maxThreads;
    this._terminateAfterDelay = terminateAfterDelay;
    this._maxConcurrentPerWorker = maxConcurrentPerWorker;
 
    const worker = this._createWebWorker();
    this._workers.push(worker);
  }
 
  /**
   const pool = WorkerPool.create({
    src: 'my-worker.js',
    // or create: () => new Worker()
    maxThreads: 2
   });
   */
 
  static create(opts) {
    Iif(!opts.create)
      opts.create = () => new Worker(opts.src);
 
    if(!opts.terminateAfterDelay)
      opts.terminateAfterDelay = 5000;
    Iif(!opts.maxThreads)
      opts.maxThreads = 2;
    Iif(!opts.maxConcurrentPerWorker) {
      opts.maxConcurrentPerWorker = 1;
    }
 
    return new WorkerPool(opts);
  }
 
  exec(...args) {
    const worker = this.getFreeWorkerOrCreate();
    if(worker)
      return this._exec(worker, 'exec', args);
 
    return new Promise(res => this._queue.push(['exec', args, res]));
  }
 
  postMessage(...args) {
    const worker = this.getFreeWorkerOrCreate();
    Eif(worker){
      return this._exec(worker, 'postMessage', args);
    }
 
    return new Promise(res => this._queue.push(['postMessage', args, res]));
  }
 
  _exec(worker, method, args) {
    return new Promise((res, rej) => {
      worker[method](...args)
        .then((result) => {
          this._onWorkDone(worker);
          res(result);
        })
        .catch(e => {
          this._onWorkDone(worker);
          rej(e);
        });
    });
  }
 
  // if there is unresolved jobs, run them
  // or remove unused workers
 
  _onWorkDone() {
    Iif(this._queue.length) {
      let worker;
      while(this._queue.length && (worker = this.getFreeWorkerOrCreate())) {
        let [method, args, cb] = this._queue.shift();
        cb(this._exec(worker, method, args));
      }
    }
 
    const freeWorkers = this.getAllFreeWorkers();
    Eif(freeWorkers.length) {
      this._waitAndRemoveWorkers(freeWorkers);
    }
  }
 
  // remove workers if its not using after delay
  _waitAndRemoveWorkers(workers) {
    setTimeout(() => {
      // only one worker should be alive always
      workers = workers.filter(w => w.isFree()).slice(0, this._workers.length - 1);
      workers.forEach(worker => this._removeWorker(worker));
    }, this._terminateAfterDelay);
  }
 
  _removeWorker(worker) {
    this._workers = this._workers.filter(w => w._id !== worker._id);
    worker.terminate();
  }
 
  getAllFreeWorkers() {
    return this._workers.filter(w => w.jobsLength() < this._maxConcurrentPerWorker);
  }
 
  getFreeWorkerOrCreate() {
    const freeWorker = this._workers.find(w => w.jobsLength() < this._maxConcurrentPerWorker);
 
    if(!freeWorker && this._workers.length < this._maxThreads) {
      const worker = this._createWebWorker();
      this._workers.push(worker);
      return worker;
    }
 
    return freeWorker;
  }
 
  _createWebWorker(){
    return new WebWorkerPromise(this._createWorker());
  }
}
 
module.exports = WorkerPool;