UNPKG

futoin-asyncsteps

Version:

Mimic traditional threads in single threaded event loop

142 lines (122 loc) 3.85 kB
'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;