File: subprocess.rst

package info (click to toggle)
libcork 0.15.0%2Bds-16
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 2,140 kB
  • sloc: ansic: 12,216; python: 206; sh: 126; makefile: 16
file content (204 lines) | stat: -rw-r--r-- 9,423 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
.. _subprocesses:

************
Subprocesses
************

.. highlight:: c

::

  #include <libcork/os.h>

The functions in this section let you fork child processes, run arbitrary
commands in them, and collect any output that they produce.


Subprocess objects
~~~~~~~~~~~~~~~~~~

.. type:: struct cork_subprocess

   Represents a child process.  There are several functions for creating child
   processes, described below.


.. function:: void cork_subprocess_free(struct cork_subprocess *sub)

   Free a subprocess.  The subprocess must not currently be executing.


Creating subprocesses
~~~~~~~~~~~~~~~~~~~~~

There are several functions that you can use to create and execute child
processes.

.. function:: struct cork_subprocess *cork_subprocess_new(void *user_data, cork_free_f free_user_data, cork_run_f run, struct cork_stream_consumer *stdout, struct cork_stream_consumer *stderr, int *exit_code)
              struct cork_subprocess *cork_subprocess_new_exec(struct cork_exec *exec, struct cork_stream_consumer *stdout, struct cork_stream_consumer *stderr, int *exit_code)

   Create a new subprocess specification.  The first variant will execute the
   given *run* function in the subprocess.  The second variant will execute a
   new program in the subprocess; the details of the program to execute are
   given by a :c:type:`cork_exec` specification object.

   For both of these functions, you can collect the data that the subprocess
   writes to its stdout and stderr streams by passing in :ref:`stream consumer
   <stream-consumers>` instances for the *stdout* and/or *stderr* parameters.
   If either (or both) of these parameters is ``NULL``, then the child process
   will inherit the corresponding output stream from the current process.
   (Usually, this means that the child's stdout or stderr will be interleaved
   with the parent's.)

   If you provide a non-``NULL`` pointer for the *exit_code* parameter, then we
   will fill in this pointer with the exit code of the subprocess when it
   finishes.  For :c:func:`cork_subprocess_new_exec`, the exit code is the value
   passed to the builtin ``exit`` function, or the value returned from the
   subprocess's ``main`` function.  For :c:func:`cork_subprocess_new`, the exit
   code is the value returned from the thread body's *run* function.


You can also create *groups* of subprocesses.  This lets you start up several
subprocesses at the same time, and wait for them all to finish.

.. type:: struct cork_subprocess_group

   A group of subprocesses that will all be executed simultaneously.

.. function:: struct cork_subprocess_group *cork_subprocess_group_new(void)

   Create a new group of subprocesses.  The group will initially be empty.

.. function:: void cork_subprocess_group_free(struct cork_subprocess_group *group)

   Free a subprocess group.  This frees all of the subprocesses in the group,
   too.  If you've started executing the subprocesses in the group, you **must
   not** call this function until they have finished executing.  (You can use
   the :c:func:`cork_subprocess_group_is_finished` function to see if the group
   is still executing, and the :c:func:`cork_subprocess_group_abort` to
   terminate the subprocesses before freeing the group.)

.. function:: void cork_subprocess_group_add(struct cork_subprocess_group *group, struct cork_subprocess *sub)

   Add the given subprocess to *group*.  The group takes control of the
   subprocess; you should not try to free it yourself.


Once you've created your subprocesses, you can start them executing:

.. function:: int cork_subprocess_start(struct cork_subprocess *sub)
              int cork_subprocess_group_start(struct cork_subprocess_group *group)

   Execute the given subprocess, or all of the subprocesses in *group*.  We
   immediately return once the processes have been started.  You can use the
   :c:func:`cork_subprocess_drain`, :c:func:`cork_subprocess_wait`,
   :c:func:`cork_subprocess_group_drain`, and
   :c:func:`cork_subprocess_group_wait` functions to wait for the subprocesses
   to complete.

   If there are any errors starting the subprocesses, we'll terminate any
   subprocesses that we were able to start, set an :ref:`error condition
   <errors>`, and return ``-1``.


Since we immediately return after starting the subprocesses, you must somehow
wait for them to finish.  There are two strategies for doing so.  If you don't
need to communicate with the subprocesses (by writing to their stdin streams or
sending them signals), the simplest strategy is to just wait for them to finish:

.. function:: int cork_subprocess_wait(struct cork_subprocess *sub)
              int cork_subprocess_group_wait(struct cork_subprocess_group *group)

   Wait until the given subprocess, or all of the subprocesses in *group*, have
   finished executing.  While waiting, we'll continue to read data from the
   subprocesses stdout and stderr streams as we can.

   If there are any errors reading from the subprocesses, we'll terminate all of
   the subprocesses that are still executing, set an :ref:`error condition
   <errors>`, and return ``-1``.  If the group has already finished, the
   function doesn't do anything.

As an example::

    struct cork_subprocess_group  *group = /* from somewhere */;
    /* Wait for the subprocesses to finish */
    if (cork_subprocess_group_wait(group) == -1) {
        /* An error occurred; handle it! */
    }

    /* At this point, we're guaranteed that the subprocesses have all been
     * terminated; either everything finished successfully, or the subprocesses
     * were terminated for us when an error was detected. */
    cork_subprocess_group_free(group);


If you do need to communicate with the subprocesses, then you need more control
over when we try to read from their stdout and stderr streams.  (The pipes that
connect the subprocesses to the parent process are fixed size, and so without
careful orchestration, you can easily get a deadlock.  Moreover, the right
pattern of reading and writing depends on the subprocesses that you're
executing, so it's not something that we can handle for you automatically.)

.. function:: struct cork_stream_consumer *cork_subprocess_stdin(struct cork_subprocess *sub)

   Return a :ref:`stream consumer <stream-consumers>` that lets you write data
   to the subprocess's stdin.  We do not buffer this data in any way; calling
   :c:func:`cork_stream_consumer_data` immediately tries to write the given data
   to the subprocess's stdin stream.  This can easily lead to deadlock if you do
   not manage the subprocess's particular orchestration correctly.

.. function:: bool cork_subprocess_is_finished(struct cork_subprocess *sub)
              bool cork_subprocess_group_is_finished(struct cork_subprocess_group *group)

   Return whether the given subprocess, or all of the subprocesses in *group*,
   have finished executing.

.. function:: int cork_subprocess_abort(struct cork_subprocess *sub)
              int cork_subprocess_group_abort(struct cork_subprocess_group *group)

   Immediately terminate the given subprocess, or all of the subprocesses in
   *group*.  This can be used to clean up if you detect an error condition and
   need to close the subprocesses early.  If the group has already finished, the
   function doesn't do anything.

.. function:: bool cork_subprocess_drain(struct cork_subprocess *sub)
              bool cork_subprocess_group_drain(struct cork_subprocess_group *group)

   Check the given subprocess, or all of the subprocesses in *group*, for any
   output on their stdout and stderr streams.  We'll read in as much data as we
   can from all of the subprocesses without blocking, and then return.  (Of
   course, we only do this for those subprocesses that you provided stdout or
   stderr consumers for.)

   This function lets you pass data into the subprocesses's stdin streams, or
   (**TODO: eventually**) send them signals, and handle any orchestration that's
   necessarily to ensure that the subprocesses don't deadlock.

   The return value indicates whether any "progress" was made.  We will return
   ``true`` if we were able to read any data from any of the subprocesses, or if
   we detected that any of the subprocesses exited.

   If there are any errors reading from the subprocesses, we'll terminate all of
   the subprocesses that are still executing, set an :ref:`error condition
   <errors>`, and return ``false``.  If the group has already finished, the
   function doesn't do anything.

To do this, you continue to "drain" the subprocesses whenever you're ready to
read from their stdout and stderr streams.  You repeat this in a loop, writing
to the stdin streams or sending signals as necessary, until all of the
subprocesses have finished::

    struct cork_subprocess_group  *group = /* from somewhere */;
    while (!cork_subprocess_group_is_finished(group)) {
        /* Drain the stdout and stderr streams */
        if (cork_subprocess_group_drain(group) == -1) {
            /* An error occurred; handle it! */
        } else {
            /* Write to the stdin streams or send signals */
        }
    }

    /* At this point, we're guaranteed that the subprocesses have all been
     * terminated; either everything finished successfully, or the subprocesses
     * were terminated for us when an error was detected. */
    cork_subprocess_group_free(group);