Thursday, October 18, 2012

Linux Launch of Other Applications from an Application



 

Overview:

 

This post will illustrate a way for an application process to launch other applications and then wait until these other applications have terminated before terminating itself.

 

In this way a "master" application can launch/start the other applications of a configuration and, using the Is_Running function of the previous post, then determine when they finish before terminating.  (Note: For the launching application to continue running while the launched applications are running seems to have importance in Linux as was pointed out by a number of internet posts that I found while searching for how to launch an application from another application.)

 

Upon succeeding in launching my previous three test applications (test1, test2, and test3) from a new fourth test application (test4) I found that the result was different than under Windows.  Under Windows when I launched three Exploratory Project (EP) DOS (that is, console) applications via the Windows Display application, a new console window opened for each one.  Therefore, their console output was displayed to their separate console windows and any of them could be terminated via a ctrl-C in its particular window. 

 

However, under Linux nothing of that sort happens.  Instead all the console output goes to the terminal window from which the launching application (that is, test4) was launched.  Therefore, the console output from test4 and that of the launched application processes (test1, test2 and test3) become intermixed in the one terminal window.

 

I tried to find a way to either create a new Linux terminal window as the new process was spawned or to have it create a window for itself as it started.  But I failed in this.  I did find that GNAT has a terminals.c file that implements functions declared in the file g-tty.ads that contains the GNAT.TTY package.  This package has an Allocate_TTY procedure to create a new TTY.  I had hopes for this but it doesn't seem to create a terminal but, instead, a special file that can be written to via normal file writes.  This doesn't cause a terminal window to be displayed.  Since I already have a log file and a Console Ada package that displays both to the terminal window and writes the text to a particular disk file, this is of no help at all.  And doesn't allow a person to force the termination of the application from its terminal window.

 

Implementation:

 

The ability to spawn another application process is supplied via the Non_Blocking_Spawn procedure of the GNAT.OS_Lib package.  (Note: This package is just another name for the System OS_Lib package which contains the actual code.)  Per Linux, as I understand it, this spawn of another process creates the process and then swaps its use to that for the provided application.

 

There are two versions of spawn, blocking and non-blocking where blocking prevents the invoking process from continuing until the spawned process terminates.  Non_Blocking_Spawn takes two parameters; the file path of the executable application to be launched and a list of arguments.  In my use, as illustrated in the code below, I provided one null argument for the list since the test applications don't input any operator parameters.

 

Then, since in my internet searches the spawning process was to continue until the spawned processes terminated, I used a loop checking via the Is_Running function to determine when all the spawned processes were no longer running.  For the Exploratory Project this behavior would need to be changed since the other applications have no natural termination and need to be terminated by the application that spawned them although, under Windows, they can be terminated by the operator via ctrl-C in their console window.

 

test4.adb:

 

procedure Test4 is

 

  subtype Remote_App_Range_Type

  --| Range of remote application processes to be launched

  is Apps.Application_Number_Type range 1..3;

 

  type Running_App_Type

  is array (Remote_App_Range_Type) of Boolean;

 

  Running_App

  --| True if launched app is running

  : Running_App_Type := ( others => False );

 

  type Launched_Process_Id_Type

  is array (Remote_App_Range_Type) of GNAT.OS_Lib.Process_Id;

 

  Launched_Process_Id

  --| Identifiers of the Launched processes to be taken over by the app

  : Launched_Process_Id_Type := ( others => GNAT.OS_Lib.Invalid_PId );

 

  type Launched_App_Name_Type

  is array (Remote_App_Range_Type) of String(1..35);

 

  Launched_App_Name

  --| Application executable names including path

  : constant Launched_App_Name_Type

  := ( 1 => "/home/clayton/Source/Test/test1.exe",

       2 => "/home/clayton/Source/Test/test2.exe",

       3 => "/home/clayton/Source/Test/test3.exe" );

 

  Status

  : Exec_mC.Status_Type;

 

  use type GNAT.OS_Lib.Process_Id;

 

begin

 

  Console.Initialize; -- output to the terminal and the log file

  Exec.Text_Log.Open( App => 4 );

  Console.Write( "Test4 started" );

 

  Exec_mC.Initialize;

 

  -- Spawn / launch the other three application processes via this one.

  declare

 

    Arg1

    --| Argument 1 of list

    : GNAT.OS_Lib.String_Access := new String(1..10);

 

    Args

    --| List of arguments

    : GNAT.OS_Lib.Argument_List(1..1);

 

  begin

 

    Arg1.all := ( others => ' ' );

    Args(1) := Arg1;

 

    Launched_Process_Id(1) :=

      GNAT.OS_Lib.Non_Blocking_Spawn

      ( Program_Name => Launched_App_Name(1),

        Args         => Args );

 

    Launched_Process_Id(2) :=

      GNAT.OS_Lib.Non_Blocking_Spawn

      ( Program_Name => Launched_App_Name(2),

        Args         => Args );

 

    Launched_Process_Id(3) :=

      GNAT.OS_Lib.Non_Blocking_Spawn

      ( Program_Name => Launched_App_Name(3),

        Args         => Args );

 

  end;

 

  --| Logic_Step:

  --|   Count number of launched applications.

 

  declare

 

    Count

    : Integer;

 

    Launched_Count

    : Integer;

 

  begin

 

    Count := 0;

    Launched_Count := 0;

    for I in Remote_App_Range_Type loop

      if Launched_Process_Id(I) /= GNAT.OS_Lib.Invalid_PId then

        Running_App(I) := True; -- assume running

        Launched_Count := Launched_Count + 1;

      end if;

    end loop;


    --| Logic_Step:

    --|   Wait for all the launched applications to finish.

 

    delay 0.5; -- Allow launched applications to start

 

    Forever:

    loop

      -- Wait for each launched app to finish running.

      for I in Remote_App_Range_Type loop

        if Running_App(I) then

          if not Exec_Itf.Is_Running( Path => Launched_App_Name(I) ) then

            Running_App(I) := False;

            Count := Count + 1;

            exit Forever when Count = Launched_Count;

          end if;

        end if;

      end loop;


    end loop Forever;

 

  end;

 

  --| Logic_Step:

  --|   Terminate.

 

  Exec.Text_Log.Close;

 

  Exec_Itf.Exit_to_OS;

 

end Test4;

 


 

No comments: