Skip to content

Commit 72d5574

Browse files
authored
Merge pull request #3009 from Skgland/process
adds predicates for spawning new processes without a shell
2 parents f91aedc + a2e19bd commit 72d5574

File tree

18 files changed

+974
-12
lines changed

18 files changed

+974
-12
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
4949
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
5050
# Cargo.toml rust-version
51-
- { os: ubuntu-22.04, rust-version: "1.85", target: 'x86_64-unknown-linux-gnu'}
51+
- { os: ubuntu-22.04, rust-version: "1.87", target: 'x86_64-unknown-linux-gnu'}
5252
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
5353
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
5454
defaults:

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ keywords = ["prolog", "prolog-interpreter", "prolog-system"]
1111
categories = ["command-line-utilities"]
1212
build = "build/main.rs"
1313
# Remember to check CI
14-
rust-version = "1.85"
14+
rust-version = "1.87"
1515

1616
[lib]
1717
crate-type = ["cdylib", "rlib"]

build/instructions_template.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,16 @@ enum SystemClauseType {
537537
UnsetEnv,
538538
#[strum_discriminants(strum(props(Arity = "2", Name = "$shell")))]
539539
Shell,
540+
#[strum_discriminants(strum(props(Arity = "8", Name = "$process_create")))]
541+
ProcessCreate,
542+
#[strum_discriminants(strum(props(Arity = "2", Name = "$process_id")))]
543+
ProcessId,
544+
#[strum_discriminants(strum(props(Arity = "3", Name = "$process_wait")))]
545+
ProcessWait,
546+
#[strum_discriminants(strum(props(Arity = "1", Name = "$process_kill")))]
547+
ProcessKill,
548+
#[strum_discriminants(strum(props(Arity = "1", Name = "$process_release")))]
549+
ProcessRelease,
540550
#[strum_discriminants(strum(props(Arity = "1", Name = "$pid")))]
541551
Pid,
542552
#[strum_discriminants(strum(props(Arity = "4", Name = "$chars_base64")))]
@@ -1825,6 +1835,11 @@ fn generate_instruction_preface() -> TokenStream {
18251835
&Instruction::CallSetEnv |
18261836
&Instruction::CallUnsetEnv |
18271837
&Instruction::CallShell |
1838+
&Instruction::CallProcessCreate |
1839+
&Instruction::CallProcessId |
1840+
&Instruction::CallProcessWait |
1841+
&Instruction::CallProcessKill |
1842+
&Instruction::CallProcessRelease |
18281843
&Instruction::CallPid |
18291844
&Instruction::CallCharsBase64 |
18301845
&Instruction::CallDevourWhitespace |
@@ -2063,6 +2078,11 @@ fn generate_instruction_preface() -> TokenStream {
20632078
&Instruction::ExecuteSetEnv |
20642079
&Instruction::ExecuteUnsetEnv |
20652080
&Instruction::ExecuteShell |
2081+
&Instruction::ExecuteProcessCreate |
2082+
&Instruction::ExecuteProcessId |
2083+
&Instruction::ExecuteProcessWait |
2084+
&Instruction::ExecuteProcessKill |
2085+
&Instruction::ExecuteProcessRelease |
20662086
&Instruction::ExecutePid |
20672087
&Instruction::ExecuteCharsBase64 |
20682088
&Instruction::ExecuteDevourWhitespace |

build/static_string_indexing.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
194194

195195
macro_rules! atom {
196196
#((#static_str_keys) => { Atom { index: #indices } };)*
197+
($name:literal) => {compile_error!(concat!("unknown static atom ", $name))};
197198
}
198199

199200
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {

src/arena.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ use ordered_float::OrderedFloat;
1414
use std::fmt;
1515
use std::fmt::Debug;
1616
use std::hash::{Hash, Hasher};
17+
use std::io::PipeReader;
18+
use std::io::PipeWriter;
1719
use std::mem;
1820
use std::mem::ManuallyDrop;
1921
use std::net::TcpListener;
2022
use std::ops::{Deref, DerefMut};
23+
use std::process::Child;
2124
use std::ptr;
2225
use std::ptr::addr_of_mut;
2326
use std::ptr::NonNull;
@@ -71,7 +74,10 @@ pub enum ArenaHeaderTag {
7174
TcpListener = 0b1000000,
7275
HttpListener = 0b1000001,
7376
HttpResponse = 0b1000010,
77+
PipeWriter = 0b1000011,
7478
Dropped = 0b1000100,
79+
PipeReader = 0b1001001,
80+
ChildProcess = 0b1001010,
7581
}
7682

7783
#[bitfield]
@@ -387,6 +393,19 @@ impl ArenaAllocated for HttpResponse {
387393
}
388394
}
389395

396+
impl ArenaAllocated for Child {
397+
type Payload = ManuallyDrop<Self>;
398+
#[inline]
399+
fn tag() -> ArenaHeaderTag {
400+
ArenaHeaderTag::ChildProcess
401+
}
402+
}
403+
impl AllocateInArena<Child> for Child {
404+
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<Child> {
405+
Child::alloc(arena, ManuallyDrop::new(self))
406+
}
407+
}
408+
390409
#[repr(C)]
391410
#[derive(Debug)]
392411
pub struct AllocSlab {
@@ -546,6 +565,15 @@ unsafe fn drop_slab_in_place(value: NonNull<AllocSlab>, tag: ArenaHeaderTag) {
546565
ArenaHeaderTag::StandardErrorStream => {
547566
drop_typed_slab_in_place!(StandardErrorStream, value);
548567
}
568+
ArenaHeaderTag::PipeReader => {
569+
drop_typed_slab_in_place!(PipeReader, value);
570+
}
571+
ArenaHeaderTag::PipeWriter => {
572+
drop_typed_slab_in_place!(PipeWriter, value);
573+
}
574+
ArenaHeaderTag::ChildProcess => {
575+
drop_typed_slab_in_place!(Child, value);
576+
}
549577
ArenaHeaderTag::NullStream => {
550578
unreachable!("NullStream is never arena allocated!");
551579
}

src/heap_print.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,6 +1789,23 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
17891789
(ArenaHeaderTag::Dropped, _value) => {
17901790
self.print_impromptu_atom(atom!("$dropped_value"));
17911791
}
1792+
(ArenaHeaderTag::ChildProcess, process) => {
1793+
1794+
let process_atom = atom!("$process");
1795+
1796+
if self.format_struct(max_depth, 1, process_atom) {
1797+
let atom = TokenOrRedirect::NumberFocus(max_depth, NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(process.id()))), op);
1798+
1799+
let process_root = self.state_stack.pop().unwrap();
1800+
1801+
self.state_stack.pop();
1802+
self.state_stack.pop();
1803+
1804+
self.state_stack.push(atom);
1805+
self.state_stack.push(TokenOrRedirect::Open);
1806+
self.state_stack.push(process_root);
1807+
}
1808+
}
17921809
_ => {
17931810
}
17941811
);

src/lib/process.pl

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
:- module(process, [
2+
process_create/3,
3+
process_id/2,
4+
process_release/1,
5+
process_wait/2,
6+
process_wait/3,
7+
process_kill/1
8+
]).
9+
10+
:- use_module(library(error)).
11+
:- use_module(library(iso_ext)).
12+
:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]).
13+
:- use_module(library(reif), [tfilter/3, memberd_t/3]).
14+
15+
16+
%% process_create(+Exe, +Args:list, +Options).
17+
%
18+
% Create a new process by executing the executable Exe and passing it the Arguments Args.
19+
%
20+
% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).
21+
%
22+
% Options is a list consisting of the following options:
23+
%
24+
% * `cwd(+Path)` Set the processes working directory to `Path`
25+
% * `process(-Process)` `Process` will be assigned a process handle for the spawned process
26+
% * `env(+List)` Don't inherit environment variables and set the variables defined in `List`
27+
% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List`
28+
% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
29+
%
30+
% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`.
31+
% `env/1` and `environment/1` may not be both specified.
32+
%
33+
% The following stdio `Spec` are available:
34+
%
35+
% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`)
36+
% * `file(+Path)` attach the strea to the file at `Path`
37+
% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)`
38+
% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream`
39+
%
40+
% Specifying an option multiple times is an error, when an option is not specified the following defaults apply:
41+
%
42+
% - `cwd(".")`
43+
% - `environment([])`
44+
% - `stdin(std)`, `stdout(std)`, `stderr(std)`
45+
%
46+
process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3).
47+
48+
process_create_(Exe, Args, Options) :-
49+
must_be(chars, Exe),
50+
must_be(list, Args),
51+
maplist(must_be(chars), Args),
52+
must_be(list, Options),
53+
check_options(
54+
[
55+
option([stdin], valid_stdio, stdin(std), stdin(Stdin)),
56+
option([stdout], valid_stdio, stdout(std), stdout(Stdout)),
57+
option([stderr], valid_stdio, stderr(std), stderr(Stderr)),
58+
option([env, environment], valid_env, environment([]), Env),
59+
option([process], valid_uninit_process, process(_), process(Process)),
60+
option([cwd], valid_cwd, cwd("."), cwd(Cwd))
61+
],
62+
Options,
63+
process_create_option
64+
),
65+
Stdin =.. Stdin1,
66+
Stdout =.. Stdout1,
67+
Stderr =.. Stderr1,
68+
simplify_env(Env, Env1),
69+
'$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
70+
71+
%% process_id(+Process, -Pid).
72+
%
73+
process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2).
74+
75+
process_id_(Process, Pid) :-
76+
valid_process(Process),
77+
must_be(var, Pid),
78+
'$process_id'(Process, Pid).
79+
80+
%% process_wait(+Process, Status).
81+
%
82+
% See `process_create/3` with `Options = []`
83+
%
84+
process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2).
85+
86+
87+
%% process_wait(+Process, Status, Options).
88+
%
89+
% Wait for the process behind the process handle `Process` to exit.
90+
%
91+
% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
92+
% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
93+
% When the process doesn't exit before the timeout `Status` will be unified with `timeout`.
94+
%
95+
% `Options` is a a list of the following options
96+
%
97+
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
98+
%
99+
% Each options may be specified at most once, when an option is not specified the following defaults apply:
100+
%
101+
% - timeout(infinite)
102+
%
103+
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
104+
105+
process_wait_(Process, Status, Options) :-
106+
valid_process(Process),
107+
check_options(
108+
[
109+
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout))
110+
],
111+
Options,
112+
process_wait_option
113+
),
114+
'$process_wait'(Process, Exit, Timeout),
115+
Exit = Status.
116+
117+
valid_timeout(timeout(infinite)).
118+
valid_timeout(timeout(0)).
119+
120+
121+
%% process_kill(+Process).
122+
%
123+
% Kill the process using the process handle `Process`.
124+
% On Unix this sends SIGKILL.
125+
%
126+
% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
127+
%
128+
process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1).
129+
130+
process_kill_(Process) :-
131+
valid_process(Process),
132+
'$process_kill'(Process).
133+
134+
%% process_release(+Process)
135+
%
136+
% wait for the process to exit (if not already) and release process handle `Process`
137+
%
138+
% It's an error if `Process` is not a valid process handle
139+
%
140+
process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1).
141+
142+
process_release_(Process) :-
143+
valid_process(Process),
144+
process_wait(Process, _),
145+
'$process_release'(Process).
146+
147+
148+
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).
149+
150+
must_be_known_options_(_, _, [], _).
151+
must_be_known_options_(Valid, Found, [X|XS], Domain) :-
152+
( functor(X, Option, 1) -> true
153+
; domain_error(Domain, X, [])
154+
) ,
155+
( member(Option, Found) -> domain_error(non_duplicate_options, Option , [])
156+
; member(Option, Valid) -> true
157+
; domain_error(Domain, Option, [])
158+
),
159+
must_be_known_options_(Valid, [Option | Found], XS, Domain).
160+
161+
check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3).
162+
163+
check_options_(KnownOptions, Options, Domain) :-
164+
maplist(option_names, KnownOptions, Namess),
165+
append(Namess, Names),
166+
must_be_known_options(Names, Options, Domain),
167+
extract_options(KnownOptions, Options).
168+
169+
option_names(option(Names,_,_,_), Names).
170+
171+
extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2).
172+
173+
extract_options_([], _).
174+
extract_options_([X | XS], Options) :-
175+
option(Kinds, Pred, Default, Choice) = X,
176+
tfilter(find_option(Kinds), Options, Solutions),
177+
( Solutions = [] -> Choice = Default
178+
; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided
179+
; domain_error(non_conflicting_options, Solutions, [])
180+
),
181+
extract_options_(XS, Options).
182+
183+
find_option(Names, Found, T) :-
184+
functor(Found, Name, 1),
185+
memberd_t(Name, Names, T).
186+
187+
valid_stdio(IO) :- arg(1, IO, Arg),
188+
( var(Arg) -> instantiation_error([])
189+
; valid_stdio_(Arg) -> true
190+
; domain_error(stdio_spec, Arg, [])
191+
).
192+
193+
valid_stdio_(std).
194+
valid_stdio_(null).
195+
valid_stdio_(pipe(Stream)) :- must_be(var, Stream).
196+
valid_stdio_(file(Path)) :- must_be(chars, Path).
197+
198+
valid_env(env(E)) :-
199+
must_be(list, E),
200+
( valid_env_(E) -> true
201+
; domain_error(process_create_option, env(E), [])
202+
).
203+
valid_env(environment(E)) :-
204+
must_be(list, E),
205+
( valid_env_(E) -> true
206+
; domain_error(process_create_option, environment(E), [])
207+
).
208+
209+
valid_env_([]).
210+
valid_env_([N=V|ES]) :-
211+
must_be(chars, N),
212+
must_be(chars, V),
213+
valid_env_(ES).
214+
215+
valid_uninit_process(process(Process)) :- must_be(var, Process).
216+
217+
valid_process(Process) :- var(Process) -> instantiation_error([]) ; true.
218+
219+
valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
220+
221+
simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1).
222+
223+
simplify_env_([],[]).
224+
simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1).

0 commit comments

Comments
 (0)