futoin-asyncsteps
Version:
Mimic traditional threads in single threaded event loop
135 lines (113 loc) • 3.56 kB
JavaScript
'use strict';
/**
* @file
* @author Andrey Galkin <andrey@futoin.org>
*
*
* Copyright 2014-2017 FutoIn Project (https://futoin.org)
* Copyright 2014-2017 Andrey Galkin <andrey@futoin.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const ISync = require( './ISync' );
const { DefenseRejected } = require( './Errors' );
const { prev_queue } = require( './lib/common' );
const throttle_sync = ( asp, throttle, step, on_error ) => {
const root = asp._root;
if ( throttle._lock( asp ) ) {
asp._on_error = on_error;
step( asp, ...asp._call_args );
} else {
asp._on_cancel = throttle._cancel_handler;
root._next_args = asp._call_args;
prev_queue( root ).unshift(
[ step, on_error ],
);
}
};
/**
* Throttling for AsyncSteps
*/
class Throttle extends ISync {
/**
* C-tor
* @param {number} [max] - maximum number of simultaneous critical section entries
* @param {number} [period_ms] - time period in milliseconds
* @param {?number} [max_queue] - limit queue length, if set
*/
constructor( max, period_ms=1e3, max_queue = null ) {
super();
this._max = max;
this._current = 0;
const queue = this._queue = [];
this._timer = null;
this._period_ms = period_ms;
this._max_queue = max_queue;
this._cancel_handler = ( asi ) => {
const idx = queue.indexOf( asi );
if ( idx >= 0 ) {
queue.splice( idx, 1 );
}
};
}
_lock( asi ) {
this._ensureTimer();
if ( this._current >= this._max ) {
const queue = this._queue;
const max_queue = this._max_queue;
if ( ( max_queue !== null ) && ( queue.length >= max_queue ) ) {
asi.error( DefenseRejected, 'Throttle queue limit' );
}
queue.push( asi );
return false;
} else {
this._current += 1;
return true;
}
}
_ensureTimer() {
if ( !this._timer ) {
this._timer = setInterval( () => {
this._resetPeriod();
}, this._period_ms );
}
}
_resetPeriod() {
this._current = 0;
const queue = this._queue;
if ( !queue.length ) {
clearInterval( this._timer );
this._timer = null;
return;
}
const max = this._max;
let current = 0;
while ( queue.length && ( current < max ) ) {
let other_as = queue.shift();
if ( other_as.state ) {
++current;
const other_root = other_as._root;
other_root._handle_success( other_root._next_args );
}
}
this._current = current;
}
sync( as, step, onerror ) {
as.add(
( as ) => {
throttle_sync( as, this, step, onerror );
},
);
}
}
module.exports = Throttle;