1
0
Fork 0
liza/src/server/rater/thread.js

226 lines
4.7 KiB
JavaScript

/**
* Rating thread
*
* Copyright (C) 2017 LoVullo Associates, Inc.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This thread permits both asynchronous rating (in the case of JS raters) as
* well as hassle-free runtime reloading of raters withour risk of memory leaks
* that may be caused by deleting entries from node's module cache.
*/
var map = {},
fs = require( 'fs' );
var rater_path = process.env.RATER_PROGRAM_PATH;
function forEachProgram( c )
{
fs.readdirSync( rater_path )
.forEach( function( filename )
{
// ignore non-js files
if ( !( filename.match( /\.js$/ ) ) )
{
return;
}
c( filename, ( rater_path + '/' + filename ) );
} );
}
// sent by parent to indicate that we should exit (we should never receive this
// signal from any sort of shell or anything)
try
{
process.on( 'SIGHUP', function()
{
// we're good
process.exit( 0 );
} );
}
catch ( e )
{
sendLog(
"WARNING: OS does not support signal handlers; thread will always " +
"appear to exit in error if reload is requested"
);
}
process.on( 'message', function( msg )
{
switch ( msg.cmd )
{
case 'rate':
doRate( msg );
break;
default:
logError( "Unknown command from parent: " + msg.cmd );
}
} );
process.on( 'uncaughtException', function( e )
{
sendError( e );
} );
// load raters
forEachProgram( function( filename, path )
{
var rater = null;
try
{
rater = require( path );
sendLog( 'Loaded rater: ' + filename );
}
catch ( e )
{
sendError( e );
}
var name = filename.replace( /\.js$/, '' );
map[ name ] = rater;
} );
sendLog( 'Rating process ready (PID ' + process.pid + ').' );
function doRate( data )
{
var quote = data.quote,
rqid = data.rqid,
rater = map[ data.supplier ];
if ( !rater )
{
process.send( {
cmd: 'rate-reply',
rqid: rqid,
status: 'error',
msg: "Unknown supplier: " + data.supplier,
} );
return;
}
// TODO: temporary, until refactoring is complete
var imported_flag = false;
var rate_quote = {
getId: function()
{
return quote.id;
},
getAgentId: function()
{
return quote.agentId;
},
getAgentName: function()
{
return quote.agentName;
},
getCreditScoreRef: function()
{
return quote.creditScoreRef;
},
getBucket: function()
{
return {
getData: function()
{
return quote.data;
}
};
},
setImported: function( val )
{
val = ( val === undefined ) ? true : !!val;
imported_flag = val;
},
};
var rate_session = {
agentId: function()
{
return data.agentId;
},
isInternal: function()
{
return data.internal;
},
};
// perform rating
rater.rate( rate_quote, rate_session, data.indv,
function( rdata, actions )
{
process.send( {
cmd: 'rate-reply',
rqid: rqid,
status: 'ok',
data: rdata,
actions: actions,
// not used by all raters
imported: imported_flag,
} );
},
function( msg )
{
process.send( {
cmd: 'rate-reply',
rqid: rqid,
status: 'error',
msg: msg,
} );
}
);
}
function sendLog( msg )
{
process.send( { cmd: 'log', msg: msg } );
}
function sendError( e )
{
process.send( {
cmd: 'error',
msg: e.message,
stack: e.stack
} );
}