207 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| Object.defineProperty(exports, '__esModule', {
 | |
|   value: true
 | |
| });
 | |
| exports.default = void 0;
 | |
| 
 | |
| var _FifoQueue = _interopRequireDefault(require('./FifoQueue'));
 | |
| 
 | |
| var _types = require('./types');
 | |
| 
 | |
| function _interopRequireDefault(obj) {
 | |
|   return obj && obj.__esModule ? obj : {default: obj};
 | |
| }
 | |
| 
 | |
| function _defineProperty(obj, key, value) {
 | |
|   if (key in obj) {
 | |
|     Object.defineProperty(obj, key, {
 | |
|       value: value,
 | |
|       enumerable: true,
 | |
|       configurable: true,
 | |
|       writable: true
 | |
|     });
 | |
|   } else {
 | |
|     obj[key] = value;
 | |
|   }
 | |
|   return obj;
 | |
| }
 | |
| 
 | |
| class Farm {
 | |
|   constructor(_numOfWorkers, _callback, options = {}) {
 | |
|     var _options$workerSchedu, _options$taskQueue;
 | |
| 
 | |
|     _defineProperty(this, '_computeWorkerKey', void 0);
 | |
| 
 | |
|     _defineProperty(this, '_workerSchedulingPolicy', void 0);
 | |
| 
 | |
|     _defineProperty(this, '_cacheKeys', Object.create(null));
 | |
| 
 | |
|     _defineProperty(this, '_locks', []);
 | |
| 
 | |
|     _defineProperty(this, '_offset', 0);
 | |
| 
 | |
|     _defineProperty(this, '_taskQueue', void 0);
 | |
| 
 | |
|     this._numOfWorkers = _numOfWorkers;
 | |
|     this._callback = _callback;
 | |
|     this._computeWorkerKey = options.computeWorkerKey;
 | |
|     this._workerSchedulingPolicy =
 | |
|       (_options$workerSchedu = options.workerSchedulingPolicy) !== null &&
 | |
|       _options$workerSchedu !== void 0
 | |
|         ? _options$workerSchedu
 | |
|         : 'round-robin';
 | |
|     this._taskQueue =
 | |
|       (_options$taskQueue = options.taskQueue) !== null &&
 | |
|       _options$taskQueue !== void 0
 | |
|         ? _options$taskQueue
 | |
|         : new _FifoQueue.default();
 | |
|   }
 | |
| 
 | |
|   doWork(method, ...args) {
 | |
|     const customMessageListeners = new Set();
 | |
| 
 | |
|     const addCustomMessageListener = listener => {
 | |
|       customMessageListeners.add(listener);
 | |
|       return () => {
 | |
|         customMessageListeners.delete(listener);
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     const onCustomMessage = message => {
 | |
|       customMessageListeners.forEach(listener => listener(message));
 | |
|     };
 | |
| 
 | |
|     const promise = new Promise( // Bind args to this function so it won't reference to the parent scope.
 | |
|       // This prevents a memory leak in v8, because otherwise the function will
 | |
|       // retaine args for the closure.
 | |
|       ((args, resolve, reject) => {
 | |
|         const computeWorkerKey = this._computeWorkerKey;
 | |
|         const request = [_types.CHILD_MESSAGE_CALL, false, method, args];
 | |
|         let worker = null;
 | |
|         let hash = null;
 | |
| 
 | |
|         if (computeWorkerKey) {
 | |
|           hash = computeWorkerKey.call(this, method, ...args);
 | |
|           worker = hash == null ? null : this._cacheKeys[hash];
 | |
|         }
 | |
| 
 | |
|         const onStart = worker => {
 | |
|           if (hash != null) {
 | |
|             this._cacheKeys[hash] = worker;
 | |
|           }
 | |
|         };
 | |
| 
 | |
|         const onEnd = (error, result) => {
 | |
|           customMessageListeners.clear();
 | |
| 
 | |
|           if (error) {
 | |
|             reject(error);
 | |
|           } else {
 | |
|             resolve(result);
 | |
|           }
 | |
|         };
 | |
| 
 | |
|         const task = {
 | |
|           onCustomMessage,
 | |
|           onEnd,
 | |
|           onStart,
 | |
|           request
 | |
|         };
 | |
| 
 | |
|         if (worker) {
 | |
|           this._taskQueue.enqueue(task, worker.getWorkerId());
 | |
| 
 | |
|           this._process(worker.getWorkerId());
 | |
|         } else {
 | |
|           this._push(task);
 | |
|         }
 | |
|       }).bind(null, args)
 | |
|     );
 | |
|     promise.UNSTABLE_onCustomMessage = addCustomMessageListener;
 | |
|     return promise;
 | |
|   }
 | |
| 
 | |
|   _process(workerId) {
 | |
|     if (this._isLocked(workerId)) {
 | |
|       return this;
 | |
|     }
 | |
| 
 | |
|     const task = this._taskQueue.dequeue(workerId);
 | |
| 
 | |
|     if (!task) {
 | |
|       return this;
 | |
|     }
 | |
| 
 | |
|     if (task.request[1]) {
 | |
|       throw new Error('Queue implementation returned processed task');
 | |
|     } // Reference the task object outside so it won't be retained by onEnd,
 | |
|     // and other properties of the task object, such as task.request can be
 | |
|     // garbage collected.
 | |
| 
 | |
|     const taskOnEnd = task.onEnd;
 | |
| 
 | |
|     const onEnd = (error, result) => {
 | |
|       taskOnEnd(error, result);
 | |
| 
 | |
|       this._unlock(workerId);
 | |
| 
 | |
|       this._process(workerId);
 | |
|     };
 | |
| 
 | |
|     task.request[1] = true;
 | |
| 
 | |
|     this._lock(workerId);
 | |
| 
 | |
|     this._callback(
 | |
|       workerId,
 | |
|       task.request,
 | |
|       task.onStart,
 | |
|       onEnd,
 | |
|       task.onCustomMessage
 | |
|     );
 | |
| 
 | |
|     return this;
 | |
|   }
 | |
| 
 | |
|   _push(task) {
 | |
|     this._taskQueue.enqueue(task);
 | |
| 
 | |
|     const offset = this._getNextWorkerOffset();
 | |
| 
 | |
|     for (let i = 0; i < this._numOfWorkers; i++) {
 | |
|       this._process((offset + i) % this._numOfWorkers);
 | |
| 
 | |
|       if (task.request[1]) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return this;
 | |
|   }
 | |
| 
 | |
|   _getNextWorkerOffset() {
 | |
|     switch (this._workerSchedulingPolicy) {
 | |
|       case 'in-order':
 | |
|         return 0;
 | |
| 
 | |
|       case 'round-robin':
 | |
|         return this._offset++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _lock(workerId) {
 | |
|     this._locks[workerId] = true;
 | |
|   }
 | |
| 
 | |
|   _unlock(workerId) {
 | |
|     this._locks[workerId] = false;
 | |
|   }
 | |
| 
 | |
|   _isLocked(workerId) {
 | |
|     return this._locks[workerId];
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.default = Farm;
 |