function initworker(pid, startRequestServer)
; %#ok Undocumented
% initialization function for MATLAB Parallel Server

% Copyright 2003-2019 The MathWorks, Inc.

if nargin < 1 || isempty(pid)
    parentWatchdogArgs = {};
else
    parentWatchdogArgs = {pid};
end

if nargin < 2
    startRequestServer = true;
end

try   
    dctErrorIfNoJVM();  
    % Set up the log handler that will stream back to the Worker
    iInitializeLogging();
    iLog('Start initworker');

    % MATLAB has been launched with -nodesktop, yet the diary output is
    % likely to be viewed on a desktop client. We set the hotlinks feature on
    % here for backwards compatibility as tasks used to be evaluated with
    % evalc, which always turns on hotlinks.
    feature hotlinks on

    % Turn off beep for workers.
    beep('off');
    
    % The watchdog kills this process when the process it watches dies
    % By default it watches the parent, but one can optionally specify
    % a different PID to watch
    iLog('Starting ParentWatchdog');
    com.mathworks.toolbox.distcomp.control.ParentWatchdog(parentWatchdogArgs{:});

    
    % Make sure this MATLAB cannot core dump, and will exit if anything goes
    % wrong.
    dct_psfcns('ensureProcessExitsOnFault');
    
    % Call the common initialization
    iLog('Calling initcommon');
    initcommon(@iLog);
    
    % Initialize NativeMethods - do this now to get early warning if
    % nativedmatlab shared library can not be loaded.
    com.mathworks.toolbox.distcomp.nativedmatlab.NativeMethods;
    
    iLog('Setting system properties from environment');
    import com.mathworks.toolbox.distcomp.util.SystemPropertyNames;
    % Set java system properties from the environment
    iSetSystemPropertyFromEnv(...
        'java.rmi.server.hostname', ...
        'HOSTNAME');
    iSetSystemPropertyFromEnv(...
        SystemPropertyNames.HOST_NAME, ...
        'HOSTNAME');
    iSetSystemPropertyFromEnv(...
        SystemPropertyNames.BASE_PORT, ...
        'BASE_PORT');
    iSetSystemPropertyFromEnv(...
        SystemPropertyNames.RMI_USE_SERVER_SPECIFIED_HOSTNAME, ...
        'USE_SERVER_SPECIFIED_HOSTNAME');
    iSetSystemPropertyFromEnv(...
        SystemPropertyNames.PEERSESSION_KEEP_ALIVE_PERIOD, ...
        'MDCS_PEERSESSION_KEEP_ALIVE_PERIOD');
    iSetSystemPropertyFromEnv(...
        SystemPropertyNames.PEERSESSION_KEEP_ALIVE_TIME_UNIT, ...
        'MDCS_PEERSESSION_KEEP_ALIVE_TIME_UNIT');
    
    iLog('Setting port range');
    % Set the port range MATLAB will use to talk to the job manager.
    iSetPortRange();
    
    % If we have been passed a certificate, add it to the 
    % ClientCertificateStore so it can be used.
    iSetCertificate();
           
    iLog('Testing DataStore export');
    iCheckDataStoreExporter();
    
    if startRequestServer
        iStartRequestServer()
    end
    
    iLog('End initworker');
catch err
    % Don't use iLog here just in case it is causing the error.
    fprintf(err.getReport());
    fprintf('Exiting MATLAB due to error in initworker\n');
    % Exit MATLAB (should send SIGKILL to be sure.)
    dct_psfcns('fastexit', 39)
end
end

% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iStartRequestServer()
address = getenv('WORKER_IPC_ADDRESS');
if isempty(address)
    error(message('parallel:internal:cluster:InitWorkerInvalidEnvironment', 'WORKER_IPC_ADDRESS'));
end
requestServerArguments = {address};

if ispc 
    % On Windows look for the WORKER_PROCESS_OWNER_SID variable
    % If set, pass it on to pctRequestServer.
    sid = getenv('WORKER_PROCESS_OWNER_SID');
    if ~isempty(sid)
        iLog('pctRequestServer will give permissions to SID %s', sid)
        requestServerArguments = {address, sid};
    end
end

% Start listening for commands
iLog('Starting pctRequestServer on %s', address);
pctRequestServer('start', requestServerArguments{:});
end

% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iSetCertificate()
encodedCertificate = getenv('WORKER_CERTIFICATE');
if ~isempty( encodedCertificate )
    iLog('Adding certificate: %s', encodedCertificate);
    certStore = com.mathworks.toolbox.distcomp.mjs.security.ClientCertificateStore.getInstance();
    certStore.setCertificateEntry('SharedSecret', encodedCertificate);
end
end

% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iCheckDataStoreExporter()
import com.mathworks.toolbox.distcomp.mjs.service.ExportConfigInfo;
import com.mathworks.toolbox.distcomp.mjs.service.ServiceExporterFactory;
import com.mathworks.toolbox.distcomp.mjs.datastore.LargeDataInvoker;

% This should have been set by initcommon
assert( LargeDataInvoker.getDesiredDataStoreSize() > 0, ...
        'LargeDataInvoker.setDesiredDataStoreSize has not been set');

% Check we can create/export a DataStore - this detects problems with
% settings that otherwise won't be found until later.
minTransferSize = 1; % Any size will do
portRange = ExportConfigInfo.getPortRange();
exporterFactory = ServiceExporterFactory(portRange(1), portRange(2));
invoker = LargeDataInvoker.createLargeDataInvoker(minTransferSize, exporterFactory.createExporter());
assert( ~isempty( invoker ), ...
        'Failed to create LargeDataInvoker');

% This next line will error if the DataStore can not be exported.
invoker.getDataStoreProxy();
invoker.dispose();
end

% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iSetPortRange()
import com.mathworks.toolbox.distcomp.control.PortConfig;
import com.mathworks.toolbox.distcomp.mjs.service.ExportConfigInfo;
basePortString = getenv('BASE_PORT');
% The minimum port to use - this skips the reserved ports
minPort = PortConfig.getMinDistcompServiceExportPort(basePortString);
maxNumPorts = str2double(getenv('MDCS_MAX_NUM_PORTS'));
ExportConfigInfo.setPortRange(minPort, minPort + maxNumPorts);
end

% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iSetSystemPropertyFromEnv(property, variable)
% Set a java system property from environment variable
value = getenv(variable);
% Will need to change this if empty string is a valid value.
if isempty(value)
    error(message('parallel:internal:cluster:InitWorkerInvalidEnvironment', variable));
end
iLog('Setting "%s" to "%s"', char(property), value);
java.lang.System.setProperty(property, value);
end


% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iInitializeLogging()

logPort = str2double( getenv('WORKER_LOG_PORT') );
if isnan(logPort)
    error(message('parallel:internal:cluster:InitWorkerInvalidEnvironment', 'WORKER_LOG_PORT'));
end

logLevel = str2double( getenv('WORKER_LOG_LEVEL') );
if isnan(logLevel)
    error(message('parallel:internal:cluster:InitWorkerInvalidEnvironment', 'WORKER_LOG_LEVEL'));
end

import com.mathworks.toolbox.parallel.pctutil.logging.*;
import com.mathworks.toolbox.distcomp.logging.SocketLogRecordHandler;
% Set up the handler to stream stuff back to the Worker
handler = SocketLogRecordHandler(logPort);
handler.setLevel(DistcompLevel.getLevelFromValue(logLevel));
formatter = DistcompSimpleFormatter();
handler.setFormatter(formatter);
RootLog.LOG.setLevel(DistcompLevel.getLevelFromValue(logLevel));
RootLog.LOG.addHandler(handler);

end
% -------------------------------------------------------------------------
%
% -------------------------------------------------------------------------
function iLog(varargin)
% initworker will log at "INFO" 
logger = com.mathworks.toolbox.distcomp.mjs.worker.PackageInfo.LOGGER;
msg = sprintf(varargin{:});
logger.info(msg);
end
