Search in sources :

Example 1 with FederateInstance

use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.

the class GeneratorBase method analyzeFederates.

/**
 * Analyze the AST to determine whether code is being mapped to
 * single or to multiple target machines. If it is being mapped
 * to multiple machines, then set the {@link #isFederated} field to true,
 * create a FederateInstance for each federate, and record various
 * properties of the federation
 *
 * In addition, for each top-level connection, add top-level reactions to the AST
 * that send and receive messages over the network.
 *
 * This class is target independent, so the target code
 * generator still has quite a bit of work to do.
 * It needs to provide the body of the sending and
 * receiving reactions. It also needs to provide the
 * runtime infrastructure that uses the dependency
 * information between federates. See the C target
 * for a reference implementation.
 */
private void analyzeFederates(LFGeneratorContext context) {
    // Next, if there actually are federates, analyze the topology
    // interconnecting them and replace the connections between them
    // with an action and two reactions.
    Reactor mainReactor = mainDef != null ? ASTUtils.toDefinition(mainDef.getReactorClass()) : null;
    if (mainDef == null || !mainReactor.isFederated()) {
        // The program is not federated.
        // Ensure federates is never empty.
        FederateInstance federateInstance = new FederateInstance(null, 0, 0, this, errorReporter);
        federates.add(federateInstance);
        federateByID.put(0, federateInstance);
    } else {
        // The Lingua Franca program is federated
        isFederated = true;
        // If the "--rti" flag is given to the compiler, use the argument from the flag.
        if (context.getArgs().containsKey("rti")) {
            setFederationRTIProperties(context);
        } else if (mainReactor.getHost() != null) {
            // If not specified, this defaults to 'localhost'
            if (mainReactor.getHost().getAddr() != null) {
                federationRTIProperties.put("host", mainReactor.getHost().getAddr());
            }
            // If not specified, this defaults to 14045
            if (mainReactor.getHost().getPort() != 0) {
                federationRTIProperties.put("port", mainReactor.getHost().getPort());
            }
            // Get the user information, if specified.
            if (mainReactor.getHost().getUser() != null) {
                federationRTIProperties.put("user", mainReactor.getHost().getUser());
            }
        }
        // Since federates are always within the main (federated) reactor,
        // create a list containing just that one containing instantiation.
        // This will be used to look up parameter values.
        List<Instantiation> mainReactorContext = new ArrayList<>();
        mainReactorContext.add(mainDef);
        // Create a FederateInstance for each top-level reactor.
        for (Instantiation instantiation : ASTUtils.allInstantiations(mainReactor)) {
            int bankWidth = ASTUtils.width(instantiation.getWidthSpec(), mainReactorContext);
            if (bankWidth < 0) {
                errorReporter.reportError(instantiation, "Cannot determine bank width! Assuming width of 1.");
                // Continue with a bank width of 1.
                bankWidth = 1;
            }
            // Create one federate instance for each instance in a bank of reactors.
            List<FederateInstance> federateInstances = new ArrayList<>(bankWidth);
            for (int i = 0; i < bankWidth; i++) {
                // Assign an integer ID to the federate.
                int federateID = federates.size();
                FederateInstance federateInstance = new FederateInstance(instantiation, federateID, i, this, errorReporter);
                federateInstance.bankIndex = i;
                federates.add(federateInstance);
                federateInstances.add(federateInstance);
                federateByID.put(federateID, federateInstance);
                if (instantiation.getHost() != null) {
                    federateInstance.host = instantiation.getHost().getAddr();
                    // The following could be 0.
                    federateInstance.port = instantiation.getHost().getPort();
                    // The following could be null.
                    federateInstance.user = instantiation.getHost().getUser();
                    /* FIXME: The at keyword should support a directory component.
                         * federateInstance.dir = instantiation.getHost().dir
                         */
                    if (federateInstance.host != null && federateInstance.host != "localhost" && federateInstance.host != "0.0.0.0") {
                        federateInstance.isRemote = true;
                    }
                }
            }
            if (federatesByInstantiation == null) {
                federatesByInstantiation = new LinkedHashMap<Instantiation, List<FederateInstance>>();
            }
            federatesByInstantiation.put(instantiation, federateInstances);
        }
        // In a federated execution, we need keepalive to be true,
        // otherwise a federate could exit simply because it hasn't received
        // any messages.
        targetConfig.keepalive = true;
        // Analyze the connection topology of federates.
        // First, find all the connections between federates.
        // For each connection between federates, replace it in the
        // AST with an action (which inherits the delay) and two reactions.
        // The action will be physical for physical connections and logical
        // for logical connections.
        replaceFederateConnectionsWithActions();
        // Remove the connections at the top level
        mainReactor.getConnections().clear();
    }
}
Also used : FederateInstance(org.lflang.federated.FederateInstance) ArrayList(java.util.ArrayList) List(java.util.List) ArrayList(java.util.ArrayList) Reactor(org.lflang.lf.Reactor) Instantiation(org.lflang.lf.Instantiation)

Example 2 with FederateInstance

use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.

the class FedLauncher method createLauncher.

/**
 * Create the launcher shell scripts. This will create one or two files
 * in the output path (bin directory). The first has name equal to
 * the filename of the source file without the ".lf" extension.
 * This will be a shell script that launches the
 * RTI and the federates.  If, in addition, either the RTI or any
 * federate is mapped to a particular machine (anything other than
 * the default "localhost" or "0.0.0.0"), then this will generate
 * a shell script in the bin directory with name filename_distribute.sh
 * that copies the relevant source files to the remote host and compiles
 * them so that they are ready to execute using the launcher.
 *
 * A precondition for this to work is that the user invoking this
 * code generator can log into the remote host without supplying
 * a password. Specifically, you have to have installed your
 * public key (typically found in ~/.ssh/id_rsa.pub) in
 * ~/.ssh/authorized_keys on the remote host. In addition, the
 * remote host must be running an ssh service.
 * On an Arch Linux system using systemd, for example, this means
 * running:
 *
 *     sudo systemctl <start|enable> ssh.service
 *
 * Enable means to always start the service at startup, whereas
 * start means to just start it this once.
 *
 * On MacOS, open System Preferences from the Apple menu and
 * click on the "Sharing" preference panel. Select the checkbox
 * next to "Remote Login" to enable it.
 *
 * In addition, every host must have OpenSSL installed, with at least
 * version 1.1.1a.  You can check the version with
 *
 *     openssl version
 *
 * @param coreFiles The files from the core directory that must be
 *  copied to the remote machines.
 * @param federates A list of federate instances in the federation
 * @param federationRTIProperties Contains relevant properties of the RTI.
 *  Can have values for 'host', 'dir', and 'user'
 */
public void createLauncher(ArrayList<String> coreFiles, List<FederateInstance> federates, LinkedHashMap<String, Object> federationRTIProperties) throws IOException {
    // NOTE: It might be good to use screen when invoking the RTI
    // or federates remotely so you can detach and the process keeps running.
    // However, I was unable to get it working properly.
    // What this means is that the shell that invokes the launcher
    // needs to remain live for the duration of the federation.
    // If that shell is killed, the federation will die.
    // Hence, it is reasonable to launch the federation on a
    // machine that participates in the federation, for example,
    // on the machine that runs the RTI.  The command I tried
    // to get screen to work looks like this:
    // ssh -t «target» cd «path»; screen -S «filename»_«federate.name» -L bin/«filename»_«federate.name» 2>&1
    // var outPath = binGenPath
    StringBuilder shCode = new StringBuilder();
    StringBuilder distCode = new StringBuilder();
    shCode.append(getSetupCode() + "\n");
    String distHeader = getDistHeader();
    Object host = federationRTIProperties.get("host");
    Object target = host;
    Object path = federationRTIProperties.get("dir");
    if (path == null)
        path = "LinguaFrancaRemote";
    Object user = federationRTIProperties.get("user");
    if (user != null) {
        target = user + "@" + host;
    }
    String RTILaunchString = getRtiCommand(federates);
    // Launch the RTI in the foreground.
    if (host.equals("localhost") || host.equals("0.0.0.0")) {
        // FIXME: the paths below will not work on Windows
        shCode.append(getLaunchCode(RTILaunchString) + "\n");
    } else {
        // Copy the source code onto the remote machine and compile it there.
        if (distCode.length() == 0)
            distCode.append(distHeader + "\n");
        String logFileName = String.format("log/%s_RTI.log", fileConfig.name);
        // Launch the RTI on the remote machine using ssh and screen.
        // The -t argument to ssh creates a virtual terminal, which is needed by screen.
        // The -S gives the session a name.
        // The -L option turns on logging. Unfortunately, the -Logfile giving the log file name
        // is not standardized in screen. Logs go to screenlog.0 (or screenlog.n).
        // FIXME: Remote errors are not reported back via ssh from screen.
        // How to get them back to the local machine?
        // Perhaps use -c and generate a screen command file to control the logfile name,
        // but screen apparently doesn't write anything to the log file!
        // 
        // The cryptic 2>&1 reroutes stderr to stdout so that both are returned.
        // The sleep at the end prevents screen from exiting before outgoing messages from
        // the federate have had time to go out to the RTI through the socket.
        RTILaunchString = getRtiCommand(federates);
        shCode.append(getRemoteLaunchCode(host, target, logFileName, RTILaunchString) + "\n");
    }
    // Index used for storing pids of federates
    int federateIndex = 0;
    for (FederateInstance federate : federates) {
        if (federate.isRemote) {
            FedFileConfig fedFileConfig = new FedFileConfig(fileConfig, federate.name);
            Path fedRelSrcGenPath = fedFileConfig.getSrcGenBasePath().relativize(fedFileConfig.getSrcGenPath());
            if (distCode.length() == 0)
                distCode.append(distHeader + "\n");
            String logFileName = String.format("log/%s_%s.log", fedFileConfig.name, federate.name);
            String compileCommand = compileCommandForFederate(federate);
            // '''«targetConfig.compiler» src-gen/«topLevelName»_«federate.name».c -o bin/«topLevelName»_«federate.name» -pthread «targetConfig.compilerFlags.join(" ")»'''
            // FIXME: Should $FEDERATION_ID be used to ensure unique directories, executables, on the remote host?
            distCode.append(getDistCode(path, federate, fedRelSrcGenPath, logFileName, fedFileConfig, compileCommand) + "\n");
            String executeCommand = executeCommandForRemoteFederate(federate);
            shCode.append(getFedRemoteLaunchCode(federate, path, logFileName, executeCommand, federateIndex++) + "\n");
        } else {
            String executeCommand = executeCommandForLocalFederate(fileConfig, federate);
            shCode.append(getFedLocalLaunchCode(federate, executeCommand, federateIndex++) + "\n");
        }
    }
    if (host.equals("localhost") || host.equals("0.0.0.0")) {
        // Local PID managements
        shCode.append("echo \"#### Bringing the RTI back to foreground so it can receive Control-C.\"" + "\n");
        shCode.append("fg %1" + "\n");
    }
    // Wait for launched processes to finish
    shCode.append(String.join("\n", "echo \"RTI has exited. Wait for federates to exit.\"", "# Wait for launched processes to finish.", "# The errors are handled separately via trap.", "for pid in \"${pids[@]}\"", "do", "    wait $pid", "done", "echo \"All done.\"") + "\n");
    // Write the launcher file.
    // Delete file previously produced, if any.
    File file = fileConfig.binPath.resolve(fileConfig.name).toFile();
    if (file.exists()) {
        file.delete();
    }
    FileOutputStream fOut = new FileOutputStream(file);
    fOut.write(shCode.toString().getBytes());
    fOut.close();
    if (!file.setExecutable(true, false)) {
        errorReporter.reportWarning("Unable to make launcher script executable.");
    }
    // Write the distributor file.
    // Delete the file even if it does not get generated.
    file = fileConfig.binPath.resolve(fileConfig.name + "_distribute.sh").toFile();
    if (file.exists()) {
        file.delete();
    }
    if (distCode.length() > 0) {
        fOut = new FileOutputStream(file);
        fOut.write(distCode.toString().getBytes());
        fOut.close();
        if (!file.setExecutable(true, false)) {
            errorReporter.reportWarning("Unable to make distributor script executable.");
        }
    }
}
Also used : Path(java.nio.file.Path) FederateInstance(org.lflang.federated.FederateInstance) FileOutputStream(java.io.FileOutputStream) FedFileConfig(org.lflang.federated.FedFileConfig) File(java.io.File)

Example 3 with FederateInstance

use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.

the class GeneratorBase method replaceConnectionFromFederate.

/**
 * Replace the connections from the specified output port for the specified federate reactor.
 * @param output The output port instance.
 * @param srcFederate The federate for which this port is an output.
 * @param federateReactor The reactor instance for that federate.
 * @param mainInstance The main reactor instance.
 */
private void replaceConnectionFromFederate(PortInstance output, ReactorInstance federateReactor, ReactorInstance mainInstance) {
    for (SendRange srcRange : output.dependentPorts) {
        for (RuntimeRange<PortInstance> dstRange : srcRange.destinations) {
            MixedRadixInt srcID = srcRange.startMR();
            MixedRadixInt dstID = dstRange.startMR();
            int dstCount = 0;
            int srcCount = 0;
            while (dstCount++ < dstRange.width) {
                int srcChannel = srcID.getDigits().get(0);
                int srcBank = srcID.get(1);
                int dstChannel = dstID.getDigits().get(0);
                int dstBank = dstID.get(1);
                FederateInstance srcFederate = federatesByInstantiation.get(srcRange.instance.parent.definition).get(srcBank);
                FederateInstance dstFederate = federatesByInstantiation.get(dstRange.instance.parent.definition).get(dstBank);
                Connection connection = srcRange.connection;
                if (connection == null) {
                    // This should not happen.
                    errorReporter.reportError(output.definition, "Unexpected error. Cannot find output connection for port");
                } else {
                    if (srcFederate != dstFederate && !connection.isPhysical() && targetConfig.coordination != CoordinationType.DECENTRALIZED) {
                        // Map the delays on connections between federates.
                        // First see if the cache has been created.
                        Set<Delay> dependsOnDelays = dstFederate.dependsOn.get(srcFederate);
                        if (dependsOnDelays == null) {
                            // If not, create it.
                            dependsOnDelays = new LinkedHashSet<Delay>();
                            dstFederate.dependsOn.put(srcFederate, dependsOnDelays);
                        }
                        // Put the delay on the cache.
                        if (connection.getDelay() != null) {
                            dependsOnDelays.add(connection.getDelay());
                        } else {
                            // To indicate that at least one connection has no delay, add a null entry.
                            dependsOnDelays.add(null);
                        }
                        // Map the connections between federates.
                        Set<Delay> sendsToDelays = srcFederate.sendsTo.get(dstFederate);
                        if (sendsToDelays == null) {
                            sendsToDelays = new LinkedHashSet<Delay>();
                            srcFederate.sendsTo.put(dstFederate, sendsToDelays);
                        }
                        if (connection.getDelay() != null) {
                            sendsToDelays.add(connection.getDelay());
                        } else {
                            // To indicate that at least one connection has no delay, add a null entry.
                            sendsToDelays.add(null);
                        }
                    }
                    FedASTUtils.makeCommunication(srcRange.instance, dstRange.instance, connection, srcFederate, srcBank, srcChannel, dstFederate, dstBank, dstChannel, this, targetConfig.coordination);
                }
                dstID.increment();
                srcID.increment();
                srcCount++;
                if (srcCount == srcRange.width) {
                    // Multicast. Start over.
                    srcID = srcRange.startMR();
                }
            }
        }
    }
}
Also used : FederateInstance(org.lflang.federated.FederateInstance) Connection(org.lflang.lf.Connection) Delay(org.lflang.lf.Delay)

Example 4 with FederateInstance

use of org.lflang.federated.FederateInstance in project lingua-franca by lf-lang.

the class PythonGenerator method doGenerate.

/**
 * Generate C code from the Lingua Franca model contained by the
 *  specified resource. This is the main entry point for code
 *  generation.
 *  @param resource The resource containing the source code.
 *  @param context Context relating to invocation of the code generator.
 */
@Override
public void doGenerate(Resource resource, LFGeneratorContext context) {
    // If there are federates, assign the number of threads in the CGenerator to 1
    if (isFederated) {
        targetConfig.threads = 1;
    }
    // Prevent the CGenerator from compiling the C code.
    // The PythonGenerator will compiler it.
    boolean compileStatus = targetConfig.noCompile;
    targetConfig.noCompile = true;
    // Force disable the CMake because
    targetConfig.useCmake = false;
    // it interferes with the Python target functionality
    int cGeneratedPercentProgress = (IntegratedBuilder.VALIDATED_PERCENT_PROGRESS + 100) / 2;
    super.doGenerate(resource, new SubContext(context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, cGeneratedPercentProgress));
    SubContext compilingFederatesContext = new SubContext(context, cGeneratedPercentProgress, 100);
    targetConfig.noCompile = compileStatus;
    if (errorsOccurred()) {
        context.unsuccessfulFinish();
        return;
    }
    String baseFileName = topLevelName;
    // Keep a separate file config for each federate
    FileConfig oldFileConfig = fileConfig;
    var federateCount = 0;
    Map<Path, CodeMap> codeMaps = new HashMap<>();
    for (FederateInstance federate : federates) {
        federateCount++;
        if (isFederated) {
            topLevelName = baseFileName + '_' + federate.name;
            try {
                fileConfig = new FedFileConfig(fileConfig, federate.name);
            } catch (IOException e) {
                throw Exceptions.sneakyThrow(e);
            }
        }
        // Don't generate code if there is no main reactor
        if (this.main != null) {
            try {
                Map<Path, CodeMap> codeMapsForFederate = generatePythonFiles(federate);
                codeMaps.putAll(codeMapsForFederate);
                PyUtil.copyTargetFiles(fileConfig);
                if (!targetConfig.noCompile) {
                    compilingFederatesContext.reportProgress(String.format("Validating %d/%d sets of generated files...", federateCount, federates.size()), 100 * federateCount / federates.size());
                    // If there are no federates, compile and install the generated code
                    new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context);
                    if (!errorsOccurred() && !Objects.equal(context.getMode(), LFGeneratorContext.Mode.LSP_MEDIUM)) {
                        compilingFederatesContext.reportProgress(String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), 100 * federateCount / federates.size());
                        // Why is this invoked here if the current federate is not a parameter?
                        pythonCompileCode(context);
                    }
                } else {
                    System.out.println(PythonInfoGenerator.generateSetupInfo(fileConfig));
                }
            } catch (Exception e) {
                throw Exceptions.sneakyThrow(e);
            }
            if (!isFederated) {
                System.out.println(PythonInfoGenerator.generateRunInfo(fileConfig, topLevelName));
            }
        }
        fileConfig = oldFileConfig;
    }
    if (isFederated) {
        System.out.println(PythonInfoGenerator.generateFedRunInfo(fileConfig));
    }
    // Restore filename
    topLevelName = baseFileName;
    if (errorReporter.getErrorsOccurred()) {
        context.unsuccessfulFinish();
    } else if (!isFederated) {
        context.finish(GeneratorResult.Status.COMPILED, topLevelName + ".py", fileConfig.getSrcGenPath(), fileConfig, codeMaps, "python3");
    } else {
        context.finish(GeneratorResult.Status.COMPILED, fileConfig.name, fileConfig.binPath, fileConfig, codeMaps, "bash");
    }
}
Also used : Path(java.nio.file.Path) FedFileConfig(org.lflang.federated.FedFileConfig) FileConfig(org.lflang.FileConfig) HashMap(java.util.HashMap) IOException(java.io.IOException) IOException(java.io.IOException) CodeMap(org.lflang.generator.CodeMap) SubContext(org.lflang.generator.SubContext) FederateInstance(org.lflang.federated.FederateInstance) FedFileConfig(org.lflang.federated.FedFileConfig)

Aggregations

FederateInstance (org.lflang.federated.FederateInstance)4 Path (java.nio.file.Path)2 FedFileConfig (org.lflang.federated.FedFileConfig)2 File (java.io.File)1 FileOutputStream (java.io.FileOutputStream)1 IOException (java.io.IOException)1 ArrayList (java.util.ArrayList)1 HashMap (java.util.HashMap)1 List (java.util.List)1 FileConfig (org.lflang.FileConfig)1 CodeMap (org.lflang.generator.CodeMap)1 SubContext (org.lflang.generator.SubContext)1 Connection (org.lflang.lf.Connection)1 Delay (org.lflang.lf.Delay)1 Instantiation (org.lflang.lf.Instantiation)1 Reactor (org.lflang.lf.Reactor)1