% Gateway to the java SMPD daemon manager as used by the local scheduler.

% Copyright 2011-2021 The MathWorks, Inc.

classdef ( Hidden, Sealed ) SmpdGateway
    properties ( Constant, GetAccess = private )
        PassPhrase = sprintf( 'MATLAB_%d', feature( 'getpid' ) );
    end
    methods ( Static, Access = private )

        function tf = shouldAttemptSmpd()
        % shouldAttemptSmpd - check the user hasn't disabled SMPD, or we're using MSMPI.
            tf = distcomp.feature( 'LocalUseMpiexec' ) && ~distcomp.feature( 'LocalUseMsmpi' );
        end

        function tf = launchSmpd(localSched)
        % launchSmpd - actually attempt to launch the SMPD daemon
        % return true if we succeeded.
            import parallel.internal.apishared.SmpdGateway;

            persistent smpdAvailable
            if isempty( smpdAvailable )
                % Choose the full path to smpd(.exe)
                cmd = fullfile( matlabroot, 'bin', dct_arch, 'smpd' );

                % Give smpd a temp filename to write stuff into if it sees fit.
                smpdFile = [tempname '.smpd'];

                % The full smpd command-line:
                %   "-anyport" says open any port - we're only expecting loopback connections,
                %     so this should be fine.
                %   "-phrase MATLAB_<pid>" keeps things unique
                %   "-smpdfile <tempname>.smpd" tells smpd to dump stuff in there if needed
                %   "-d 0" says run in the foreground, printing nothing as debug info.
                cmdAndArgs = { cmd, '-anyport', '-phrase', SmpdGateway.PassPhrase, ...
                               '-smpdfile', smpdFile, '-d', '0' };

                % Additions to the environment injected here, namely the PID to
                % watch, and set up MW_LICENSE_OVERRIDE for the workers to match
                % whatever is currently in lmsearchpath.
                % As per g993574, force SMPD_INTERFACE_HOSTNAME to be 127.0.0.1
                % for Windows hosts that cannot resolve their own FQDN.
                names = { 'MPICH_PID_SENTINEL', ...
                          'SMPD_INTERFACE_HOSTNAME' };
                values = { num2str( system_dependent( 'getpid' ) ), ...
                           '127.0.0.1' };
                % Note that for MPICH2 we must append the licensing environment variables to the
                % smpd startup environment so that it is automatically inherited
                % by the workers as MPICH2 cannot correctly send a very large
                % environment variable to a child process using the "-genvlist" method.
                [names, values] = parallel.internal.cluster.appendLicenseEnvVars(names, values);
                % The java code will attempt to delete the smpdfile at a later stage.
                try
                    localSched.initSmpdDaemon( cmdAndArgs, names, values, smpdFile );
                    smpdAvailable = true;
                catch E
                    % Display problems associated with launch.
                    iHandleLaunchFailedException(E);
                    smpdAvailable = false;
                end
            end
            tf = smpdAvailable;
        end
    end

    methods ( Static )
        function tf = canUseSmpd(localSched)
        % canUseSmpd - should we use SMPD for local scheduler parallel jobs
        % returns true if the user hasn't disabled SMPD, and we successfully launched
        % SMPD. Warns if we tried and failed to launch SMPD.
            import parallel.internal.apishared.SmpdGateway;

            if ~SmpdGateway.shouldAttemptSmpd()
                tf = false;
            else
                tf = SmpdGateway.launchSmpd(localSched);
                if ~tf
                    warning(message('parallel:cluster:LocalNoSMPD'));
                end
            end
        end

        function cmdArray = getMpiexecArgs( envNames )
        % getMpiexecArgs - return a cell array of arguments for use with mpiexec
        % - envNames: the list of environment variable names to forward
        % returns
        % - cmdArray: cell array of arguments to mpiexec.
            import parallel.internal.apishared.SmpdGateway;

            % Calculate the full path to the mpiexec executable
            cmd = fullfile( matlabroot, 'bin', dct_arch, 'mpiexec2' );

            % Build the genvlist portion
            eNames = sprintf( '%s,', envNames{:} );
            eArgs = { '-genvlist', eNames(1:end-1) };

            % Concoct the complete argument list
            cmdArray = [cmd, eArgs, {'-phrase', SmpdGateway.PassPhrase, '-l'  }];
        end
    end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% log (at level 0) the message from the java exception from
% supplyLaunchInformation.
function iHandleLaunchFailedException(E)
    logit = @(varargin) dctSchedulerMessage(0, varargin{:});
    if isa(E, 'matlab.exception.JavaException')
        logit('Failed to launch smpd process. Error stack:');
        stackTrace = char(com.mathworks.jmi.OpaqueJavaInterface.OpaqueJavaInterface.getExceptionMessage(...
            E.ExceptionObject));
        logit('%s', stackTrace);
    else
        logit('Unexpected failure launching smpd: %s.', getReport(E));
    end
end
