DEV Community

Kray-G
Kray-G

Posted on • Edited on

Kinx Library - Process

The script language Kinx is published with the concept of Looks like JavaScript, Feels like Ruby, Stable like AC/DC(?).

I did introduce the Kinx at this post before, but I feel documents are too few for a lot of people to know how to use it. So I decided I should post some information about Kinx. Especially for undocumented things on README.md, QuickReference.md, or Kinx Specification.

Of course, although I don't know who need it, I also decided I don't care about it. I will be glad if there is one person who is waiting for this.

Anyway, it is Process this time. It is because this will be useful in practice but it is not documented yet.

Child Process in Kinx

Process class

using Process

The Process library is not built-in, so it is explicitly loaded using the using directive.

using Process;
Enter fullscreen mode Exit fullscreen mode

Exec

Create a Process object with new Process(command, opts). The argument is a command name and an array of arguments, or a command string. In the case of an array, it is like passing arguments separately, and in the case of a command line string, it is analyzed internally and automatically decomposed into an array format.

  • Array: for example, ["ls", "-1"].
  • String: for example, "ls -1".

The created process object has the following methods.

Method Overview
run() Starts the process.
launch() Starts the process and detaches it.
std() Returns the options passed as arguments.
{ in: opts.in, out: opts.out, err: opts.err }

Not done yet only with created a new object. It is started when run() or launch() is called. run() returns an object of the ProcessController class to control the child process.

Run

By doing run(), an instance of ProcessController class is returned.

var p = new Process(["cmd","arg1"]).run();
Enter fullscreen mode Exit fullscreen mode

Launch

launch() returns nothing, which means it returns null. This is the method of not taking care of the child after starting.

new Process(["cmd","arg1"]).launch();
Enter fullscreen mode Exit fullscreen mode

ProcessController

The ProcessController class instance returned by run() has the following methods.

Method Outline
isAlive() true if the process is alive, false if it has already exited, or false after being detach
wait() Waits for the end of the process, and then returns the exit code of the process. After detach, 0 is returned.
detach() Detaches the process.

detach() is to detach the process after starting it. On Linux, the operation is a little different from when detached with launch(), but what to be done is the same. On Windows, the internal operation is absolutely the same.

On Linux, it is detached by the so-called double-fork method in order to detach it at process startup, but this can only be used at process startup. It is practically impossible to detach it after the process is started, and the child will survive as a zombie unless it is properly wait or waitpid in the parent process.

So, right after doing detach(), Kinx starts a thread just for waitpid and takes care of the child until death.

By the way, double-fork on Linux is...

  • When the parent process dies, the child process will be connected to the init process and the init process will do wait for that child.

By using the functionality of the init above, you can fork again from the process that was once forked, then by quickly terminating the first forked process and let init manage the grandchild process.

The top parent process must not forget the waitpid for the child that first forked. Only the grandchild is the target what you let the init process take care of. Of course Kinx is properly doing it, so you don't have to care about those.

Wait

An example of waiting for the end and acquiring the end code is as follows.

var p = new Process(["cmd", "arg1"]).run();
var status = p.wait();
Enter fullscreen mode Exit fullscreen mode

If you are doing detach, you cannot get it, then 0 is returned.

Detach

This is the detach but I have already described above. The process can also be detached by detach after starting the process. If detached successfully, there becomes nothing every relationship between the process and the child. You don't have to do wait and wait for the end. Or rather, you can't do it even if you want to care.

var p = new Process(["cmd", "arg1"]).run();
p.detach();
Enter fullscreen mode Exit fullscreen mode

Pipe

This section it is about the pipe everybody wants to use. The main purpose of making Process is a pipe. The most desired function is that you can freely connect standard input/output with child processes by a pipe to exchange information.

Specify the pipe with opts of new Process(cmd, opts). The following three types of parameters are available.

Parameter Outline
in Specify standard input.
Can specify a pipe object, character string, or $stdin
out Specify standard output.
Can specify a pipe object, string, $stdout, or $stderr
err Specifies the standard error output.
Can specify a pipe object, string, $stdout, or $stderr
  • Pipe object ... An object for using a pipe. Details will be described later.
  • Character string ... Input source, output destination file as file name.
  • $stdin, $stdout, $stderr ... Bind the input source and output destination of the child process to the standard input/output of this proocess.

Pipe object

Create a pipe object by new Pipe(), and it returns an array of two objects, [Read, Write]. The pipe object has the following methods for each read and write.

Normally, specify the Write pipe as the out or err of the child process, and read from the Read pipe.

Read Pipe

Do not close the pipe before doing run() because it is set after doing run().

Method Outline
peek() Returns 0 if there is no data in the pipe, and a number greater than 0 if there. -1 is an error.
read() Gets all pipe data as a string. If there is no data, it returns an empty string.
close() Closes the pipe.
Write Pipe

Do not close the pipe before doing run() because it is set after doing run().

Method Outline
write(data) Writes data to the pipe. When not all can be written, the number of written bytes will be returned.
close() Closes the pipe.
Example

The general form is as follows.

using Process;

var [r1, w1] = new Pipe();
var p1 = new Process([ "ls", "-1" ], { out: w1 }).run();
w1.close(); // You can close it as it is no longer used
while (p1.isAlive() || r1.peek() > 0) {
    var buf = r1.read();
    if (buf.length() < 0) {
        System.println("Error...");
        return -1;
    } else if (buf.length() > 0) {
        System.print(buf);
    } else {
        // System.println("no input...");
    }
}
System.println("");
Enter fullscreen mode Exit fullscreen mode

When using Write Pipe on the parent process side, it looks like this.

using Process;

// stdin read from pipe and output to standard output
[r1, w1] = new Pipe();
var p1 = new Process("cat", { in: r1, out: $stdout }).run();
r1.close(); // You can close it as it is no longer used

// send to stdin of p1
var nwrite = w1.write("Message\n");
w1.close(); // Pipe close, transmission end

p1.wait();
Enter fullscreen mode Exit fullscreen mode

By the way, this way you can control the standard output and standard error output.

new Process("cmd", { out: $stdout, err: $stdout }); // merge standard error output to standard output
new Process("cmd", { out: $stderr, err: $stderr }); // join standard output to standard error output
new Process("cmd", { out: $stderr, err: $stdout }); // swap
Enter fullscreen mode Exit fullscreen mode

Pipeline

Since connecting pipes is a rather troublesome work (in other words, which one is read...?), I also defined a Process.pipeline that does all at once. Put a callback function at the end and use it as follows.

var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */) { &(i, o, pipeline):
    // i ... write pipe to stdin for first command
    // o ... read pipe from stdout of last command
    // pipeline ... pipeline object
    //    pipeline.input ............. same as i above
    //    pipeline.output ............ same as o above
    //    pipeline.peek() ............ same as pipeline.output.peek()
    //    pipeline.read() ............ same as pipeline.output.read()
    //    same as pipeline.write() ... pipeline.input.write()
    //    pipeline.isAlive() ......... true if any process in the pipeline is alive
    //    pipeline.wait() ............ waits for all processes in the pipeline to complete,
    //                                 return the exit code as an array

    // The return value of the callback becomes the return value of Process.pipeline() as it is.
    return pipeline.wait();
};
Enter fullscreen mode Exit fullscreen mode

The last block is a callback function. This can be included in the argument as the last parameter as follows. But I added this separated syntax by a recent requests.

// Kinx has prepared following syntax
//   if you use a lambda in function call.
// This means a function object including lambda
//   can be passed by arguments.
// A function object can be set not only to
//   the last argument but also to any place in arguments.

var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */, &(i, o, pipeline) => {
    ...
});

// or

var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */, function(i, o, pipeline) {
    ...
});

// Recently, Kinx supports a block as a function object
//   if the last argument is a function.
// This block syntax can be put outside
//   function call argument list.

var r = Process.pipeline(cmd1, cmd2, cmd3/* , ... */) { (i, o, pipeline):
    ...
};
Enter fullscreen mode Exit fullscreen mode

It can be used even without calling back.

var pipeline = Process.pipeline(cmd1, cmd2, cmd3 /* , ... */);
// pipeline ... pipeline object
// omitted below.
Enter fullscreen mode Exit fullscreen mode

Conclusion

It is a good to use the child process by the script as a unified manner because the way of dealing with it is different between Windows and Linux. However, the commands themselves are different. I'm a Windows user, but I use UnxUtils to allow some Unix commands to be available at the command prompt as well. (I don't like Cygwin because it changes the environment...)

So, see you next time, if someone waits for me.

Top comments (0)