futoin-asyncsteps
Version:
Mimic traditional threads in single threaded event loop
142 lines (122 loc) • 3.85 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 mtx_sync = ( asp, mtx, step, on_error ) => {
const root = asp._root;
const release_step = ( asi ) => {
mtx._release( root );
root._handle_success( asi._call_args );
};
asp._on_cancel = mtx._release_handler;
asp._queue = [
[
( asi ) => {
if ( mtx._lock( asp, root ) ) {
root._handle_success( asp._call_args );
} else {
asi.waitExternal();
asi._call_args = asp._call_args;
}
},
undefined,
],
[ step, on_error ],
[ release_step, undefined ],
];
};
/**
* Mutual exclusion mechanism for AsyncSteps
*/
class Mutex extends ISync {
/**
* C-tor
* @param {number} [max] - maximum number of simultaneous critical section entries
* @param {?number} [max_queue] - limit queue length, if set
*/
constructor( max = 1, max_queue = null ) {
super();
this._max = max;
this._locked = 0;
this._owners = new WeakMap();
this._queue = [];
this._max_queue = max_queue;
this._release_handler = ( asi ) => {
this._release( asi._root );
};
}
_lock( asi, root ) {
const owners = this._owners;
const owned = owners.get( root );
if ( owned ) {
owners.set( root, owned + 1 );
return true;
} else if ( this._locked >= this._max ) {
const queue = this._queue;
const max_queue = this._max_queue;
if ( ( max_queue !== null ) && ( queue.length >= max_queue ) ) {
root.error( DefenseRejected, 'Mutex queue limit' );
}
queue.push( asi );
return false;
} else {
this._locked += 1;
owners.set( root, 1 );
return true;
}
}
_release( root ) {
const owners = this._owners;
const owned = owners.get( root );
if ( owned ) {
if ( owned > 1 ) {
owners.set( root, owned - 1 );
return;
}
owners.delete( root );
this._locked -= 1;
const queue = this._queue;
while ( queue.length ) {
const other_as = queue.shift();
if ( other_as.state ) {
const other_root = other_as._root;
this._lock( other_as, other_root );
other_root._handle_success( other_as._call_args );
break;
}
}
} else {
const idx = this._queue.indexOf( root );
if ( idx >= 0 ) {
this._queue.splice( idx, 1 );
}
}
}
sync( as, step, onerror ) {
as.add(
( as ) => {
mtx_sync( as, this, step, onerror );
},
);
}
}
module.exports = Mutex;