process/spawn.fz
# This file is part of the Fuzion language implementation.
#
# The Fuzion language implementation is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the License.
#
# The Fuzion language implementation is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
# License for more details.
#
# You should have received a copy of the GNU General Public License along with The
# Fuzion language implementation. If not, see <https://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------
#
# Tokiwa Software GmbH, Germany
#
# Source code of Fuzion standard library feature process.spawn
#
# -----------------------------------------------------------------------
private:public Process(id i64, std_in option i64, std_out, std_err i64) ref is
# NYI use this to register open resource
uid := unique_id
# how many bytes are read at a time
# POSIX.1 requires PIPE_BUF to be at least 512 bytes
# NYI move this to effect?
pipe_buffer_size := 4096
# read bytes from standard out
#
read_bytes choice (array u8) io.end_of_file error
=>
arr := array u8 pipe_buffer_size i->0
res := fuzion.sys.pipe.read std_out arr.internal_array.data arr.count
if res = -1
error "error reading from stdout."
else if res = 0
io.end_of_file
else
arr
.slice 0 res
.as_array
# write bytes to stdin of child process
#
public write_bytes (bytes Sequence u8) outcome i32
=>
match std_in
nil =>
error "This process does not have a standard in."
n i64 =>
write_bytes n bytes
# helper function
#
write_bytes (desc i64, bytes Sequence u8) outcome i32
pre bytes.count > 0
=>
bytes
.chunk pipe_buffer_size
.reduce_or_error 0 ((r, t) ->
arr := t.as_array
bw := fuzion.sys.pipe.write desc arr.internal_array.data arr.count
if bw = -1
abort (outcome i32) (error "error while writing. wrote $r bytes already.")
else
r+bw
)
# read up to n bytes of stdout to string
#
public read_string(n i32) outcome String =>
rp :=
ref : io.Read_Provider
read(count i32) choice (array u8) io.end_of_file error =>
read_bytes
(io.buffered.reader rp pipe_buffer_size [])
.with (()->io.buffered.read_string n)
# read string, up to 1MB from stdout
#
public read_string outcome String =>
read_string 1E9
# write string to stdin of child process
#
public write_string (s String) outcome i32 =>
write_bytes s.utf8
# wait for process to finish
#
public wait outcome u32 =>
# NYI make sure std_in can not be used anymore
if std_in!! || fuzion.sys.pipe.close std_in.get = 0
r := fuzion.sys.process.wait id
if r<0
# NYI can we be more specific here?
error "Waiting for process with id $id was unsuccessful."
else
# NYI make sure process is not used anymore
r.as_u32
else
# NYI can we be more specific here?
error "Standard-in of process could not be closed successfully."
# dispose process, cleanup
#
module dispose outcome unit =>
# NYI make sure std_out, std_err can not be used anymore
if fuzion.sys.pipe.close std_out = -1
error "standard out could not be closed successfully."
else if (fuzion.sys.pipe.close std_err = -1)
error "standard error could not be closed successfully."
# NYI cleanup process handle
# fuzion.sys.process.close id
else
unit
# pipe this processes output to new process
#
public infix | (process_and_args array String) outcome Process ! env_vars
pre
(process_and_args ∀ str -> str.is_ascii)
!process_and_args[0].contains_whitespace
=>
(spawn0 process_and_args).bind Process tup->
(pid, std_in, std_out, std_err) := tup
# NYI make sure std_out, std_err are not used anymore
p := Process pid nil std_out std_err
# NYI should wire pipe directly to process
# NYI this works only when pipes buffer is large enough for
# what is being written/read to/from pipes
# thread to pipe from one process to other
concur.thread.spawn ()->
for p1rb := read_bytes
while
match p1rb
s array u8 => (write_bytes std_in s).ok
* => false
# NYI error handling
fuzion.sys.pipe.close std_in
fuzion.sys.pipe.close Process.this.std_out
_ := fuzion.sys.pipe.close Process.this.std_err
p
# spawn a new process
#
# NOTE: environment variables that should be passed to the process
# will be taken from effect process.env_vars
#
public spawn(process_and_args array String) outcome Process ! env_vars
pre
(process_and_args ∀ str -> str.is_ascii)
!process_and_args[0].contains_whitespace
=>
(spawn0 process_and_args).bind Process (tup)->
(id, std_in, std_out, std_err) := tup
Process id std_in std_out std_err
# spawn process with option to pass environment variables
#
# helper feature for spawn and `infix |`
#
spawn0(process_and_args array String) outcome (tuple i64 i64 i64 i64)
pre process_and_args.length > 0
# NYI allow utf-8?
process_and_args ∀ (x -> x.as_codepoints ∀ (y -> y.is_ascii))
=>
sys => fuzion.sys
NULL := [u8 0].internal_array.data
# posix_spawn needs last arg to be NULL
arg_data := array fuzion.sys.Pointer process_and_args.count+1 (i -> if i<process_and_args.length then sys.c_string process_and_args[i] else NULL)
env_var_strings := env_vars
.items
.map (x ->
(k, v) := x
"$k=$v")
.as_array
# posix_spawn needs last arg to be NULL
env_data := array fuzion.sys.Pointer env_var_strings.count+1 (i -> if i<env_var_strings.count then sys.c_string env_var_strings[i] else NULL)
# args as string for windows to avoid malloc in backend
args_str := sys.c_string (String.type.join process_and_args " ")
# environment variables for windows to avoid malloc in backend
# NULL terminates each environment variable
# NULL is also used to terminate the environment variables data structure.
env_str := sys.c_string ((String.type.join env_var_strings (codepoint 0)) + (codepoint 0))
res_data := array i64 4 i->0
if (sys.process.create arg_data.internal_array.data arg_data.count env_data.internal_array.data env_data.count res_data.internal_array.data args_str env_str) = -1
error "*** error creating process ***"
else
(res_data[0], res_data[1], res_data[2], res_data[3])
last changed: 2024-03-07