ZPROC(3)
π¦ ZPROC π¦
okay bestie, welcome to ZPROC(3), straight out of the CZMQ Manual π this one's a whole mood β it's the class that runs external processes for you and wires their stdin/stdout/stderr up to ZeroMQ sockets. it's giving supervisor energy, lowkey. but also it's a draft, so strap in πΈ
β¨ NAME β¨
zproc β Class for process configuration and status π«Ά
π SYNOPSIS π
so before we even get to the code, bestie, hear me: this is a draft class and it may change without notice π it's disabled in stable builds by default. if you actually use this in applications, pls go ask for it to be pushed to stable state. and to enable it at all you need --enable-drafts. k, now the goods:
// This is a draft class, and may change without notice. It is disabled in
// stable builds by default. If you use this in applications, please ask
// for it to be pushed to stable state. Use --enable-drafts to enable.
#ifdef CZMQ_BUILD_DRAFT_API
// *** Draft method, for development use, may change without warning ***
// Create a new zproc.
// NOTE: On Windows and with libzmq3 and libzmq2 this function
// returns NULL. Code needs to be ported there.
CZMQ_EXPORT zproc_t *
zproc_new (void);
// *** Draft method, for development use, may change without warning ***
// Destroy zproc, wait until process ends.
CZMQ_EXPORT void
zproc_destroy (zproc_t **self_p);
// *** Draft method, for development use, may change without warning ***
// Return command line arguments (the first item is the executable) or
// NULL if not set.
// Caller owns return value and must destroy it when done.
CZMQ_EXPORT zlist_t *
zproc_args (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// Setup the command line arguments, the first item must be an (absolute) filename
// to run.
CZMQ_EXPORT void
zproc_set_args (zproc_t *self, zlist_t **arguments);
// *** Draft method, for development use, may change without warning ***
// Setup the command line arguments, the first item must be an (absolute) filename
// to run. Variadic function, must be NULL terminated.
CZMQ_EXPORT void
zproc_set_argsx (zproc_t *self, const char *arguments, ...);
// *** Draft method, for development use, may change without warning ***
// Setup the environment variables for the process.
CZMQ_EXPORT void
zproc_set_env (zproc_t *self, zhash_t **arguments);
// *** Draft method, for development use, may change without warning ***
// Connects process stdin with a readable ('>', connect) zeromq socket. If
// socket argument is NULL, zproc creates own managed pair of inproc
// sockets. The writable one is then accessbile via zproc_stdin method.
CZMQ_EXPORT void
zproc_set_stdin (zproc_t *self, void *socket);
// *** Draft method, for development use, may change without warning ***
// Connects process stdout with a writable ('@', bind) zeromq socket. If
// socket argument is NULL, zproc creates own managed pair of inproc
// sockets. The readable one is then accessbile via zproc_stdout method.
CZMQ_EXPORT void
zproc_set_stdout (zproc_t *self, void *socket);
// *** Draft method, for development use, may change without warning ***
// Connects process stderr with a writable ('@', bind) zeromq socket. If
// socket argument is NULL, zproc creates own managed pair of inproc
// sockets. The readable one is then accessbile via zproc_stderr method.
CZMQ_EXPORT void
zproc_set_stderr (zproc_t *self, void *socket);
// *** Draft method, for development use, may change without warning ***
// Return subprocess stdin writable socket. NULL for
// not initialized or external sockets.
CZMQ_EXPORT void *
zproc_stdin (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// Return subprocess stdout readable socket. NULL for
// not initialized or external sockets.
CZMQ_EXPORT void *
zproc_stdout (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// Return subprocess stderr readable socket. NULL for
// not initialized or external sockets.
CZMQ_EXPORT void *
zproc_stderr (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// Starts the process, return just before execve/CreateProcess.
CZMQ_EXPORT int
zproc_run (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// process exit code
CZMQ_EXPORT int
zproc_returncode (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// PID of the process
CZMQ_EXPORT int
zproc_pid (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// return true if process is running, false if not yet started or finished
CZMQ_EXPORT bool
zproc_running (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// The timeout should be zero or greater, or -1 to wait indefinitely.
// wait or poll process status, return return code
CZMQ_EXPORT int
zproc_wait (zproc_t *self, int timeout);
// *** Draft method, for development use, may change without warning ***
// send SIGTERM signal to the subprocess, wait for grace period and
// eventually send SIGKILL
CZMQ_EXPORT void
zproc_shutdown (zproc_t *self, int timeout);
// *** Draft method, for development use, may change without warning ***
// return internal actor, useful for the polling if process died
CZMQ_EXPORT void *
zproc_actor (zproc_t *self);
// *** Draft method, for development use, may change without warning ***
// send a signal to the subprocess
CZMQ_EXPORT void
zproc_kill (zproc_t *self, int signal);
// *** Draft method, for development use, may change without warning ***
// set verbose mode
CZMQ_EXPORT void
zproc_set_verbose (zproc_t *self, bool verbose);
// *** Draft method, for development use, may change without warning ***
// Self test of this class.
CZMQ_EXPORT void
zproc_test (bool verbose);
#endif // CZMQ_BUILD_DRAFT_API
and then, right at the end of the synopsis, there's this lil note the doc generator left behind:
Please add '@interface' section in './../src/zproc.c'.
(yeah it literally says that. iconic of it π)
π DESCRIPTION π
zproc β process configuration and status, plus unix pipes on steroids β¨
Warning thezprocclass has several limitations atm (as in, right this moment, bestie): - it's tested onzmq4on Linux and OSX π - it does not work on Windows, where you get empty stubs for most of the methods π - it does not work onlibzmq3andlibzmq2. we have experienced stalls and timeouts when running tests against such old versions π
Note: zproc is not yet stable, so there are no guarantees regarding API stability. some methods can have weird semantics or strange API. (it's in its messy era, we respect that π«Ά)
so here's the tea, bestie: class zproc runs an external process and uses ZeroMQ sockets to communicate with it. in other words, standard input and outputs MAY be connected with appropriate zeromq sockets, and the data flow is managed by zproc itself. that makes zproc the best in class way how to run and manage sub processes. periodt. π
data are sent and received as zframes (zframe_t), so zproc does not try to interpret the content of the messages in any way, not even a little. see the test example on how to use it π
+----------------------------------------+
| /bin/cat cat /etc/passwd |
| stdin | stdout | stderr |
|------||--------||---------------||-----|
| fd1 fd2 fd3 |
| ^ v v |
|zmq://stdin |zmq://stdout |zmq://stderr |
| [zproc supervisor] |
+----------------------------------------+
----------> zeromq magic here <-----------
+----------------------------------------+
|zmq://stdin |zmq://stdout |zmq://stderr |
| |
| consumer |
| |
| |
+----------------------------------------+
and one more lil doc-generator leftover at the very end of the description section:
Please add @discuss section in ./../src/zproc.c.
(still waiting on that one fr fr π)
part 1 done bestie, don't go anywhere β more of this page is coming π
π¦ EXAMPLE π¦
bestie this is the part where it gets real β no more abstract vibes, just actual code from the zproc_test method. it's giving "show don't tell" and honestly? obsessed. π
From zproc_test method.
first up, the setup β file is your path to the zsp executable, and then there's the very limited Windows era π:
// variable file contains path to zsp executable:
// char *file = "path/to/zsp";
#if defined (__WINDOWS__)
printf ("Very limited (on Windows) ");
{
zsys_init ();
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
zproc_set_argsx (self, file, "-v", NULL);
zproc_run (self);
zclock_sleep (100); // to let actor start the process
assert (zproc_pid (self));
zproc_kill (self, SIGTERM);
assert (zproc_returncode (self) == 255);
zproc_destroy (&self);
}
printf ("OK\n");
return;
#endif
β¨ Test case #1 β run a command, wait till it ends, grab the (standard) output. classic. πΈ
{
// Test case #1: run command, wait until it ends and get the (stdandard) output
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
// join stdout of the process to zeromq socket
// all data will be readable from zproc_stdout socket
assert (!zproc_stdout (self));
zproc_set_stdout (self, NULL);
assert (zproc_stdout (self));
zproc_set_argsx (self, file, "--help", NULL);
if (verbose)
zsys_debug("zproc_test() : launching helper '%s' --help", file );
int r = zproc_run (self);
assert (r == 0);
zframe_t *frame;
zsock_brecv (zproc_stdout (self), "f", &frame);
assert (frame);
assert (zframe_data (frame));
// TODO: real test
if (verbose)
zframe_print (frame, "1:");
zframe_destroy (&frame);
r = zproc_wait (self, -1);
assert (r == 0);
zproc_destroy (&self);
}
π Test case #2 β run the zsp helper with content written on stdin and check if it got passed to stdout. and here's where we hit the messy part, bestie, because there is a BUG lurking. the comments say it out loud:
FIXME: there is a BUG in zproc somewhere, you can't gen an output from both stdout/stderr
so the --stdout line and the stdout-reading chunk are all commented out, and the test only reads from stderr. not me side-eyeing the source code π
{
// Test case#2: run zsp helper with a content written on stdin, check if it was passed to stdout
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
// forward input from stdin to stderr
zproc_set_argsx (self, file, "--stdin", "--stderr", NULL);
// FIXME: there is a BUG in zproc somewhere, you can't gen an output from both stdout/stderr
//zproc_set_argsx (self, file, "--stdin", "--stdout", "--stderr", NULL);
zproc_set_stdin (self, NULL);
// FIXME: the bug
//zproc_set_stdout (self, NULL);
zproc_set_stderr (self, NULL);
// send data to stdin
int r = zproc_run (self);
assert (r == 0);
zframe_t *frame = zframe_new ("Lorem ipsum\0\0", strlen ("Lorem ipsum")+2);
assert (frame);
zsock_bsend (zproc_stdin (self), "f", frame);
zframe_destroy (&frame);
// FIXME: the bug
//zproc_set_stdout (self, NULL);
// read data from stdout
/*
zsys_debug ("BAF1");
zsock_brecv (zproc_stdout (self), "f", &frame);
zsys_debug ("BAF2");
assert (frame);
assert (zframe_data (frame));
if (verbose)
zframe_print (frame, "2.stdout:");
assert (!strncmp ((char*) zframe_data (frame), "Lorem ipsum", 11));
*/
// read data from stderr
zsock_brecv (zproc_stderr (self), "f", &frame);
assert (frame);
assert (zframe_data (frame));
if (verbose)
zframe_print (frame, "2.stderr:");
assert (!strncmp ((char*) zframe_data (frame), "Lorem ipsum", 11));
zproc_kill (self, SIGTERM);
zproc_wait (self, -1);
zframe_destroy (&frame);
zproc_destroy (&self);
}
π« Test case #3 β run a binary that literally does not exist. zproc_run says -1 and honestly? fair.
{
// Test case#3: run non existing binary
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
// forward input from stdin to stderr
zproc_set_argsx (self, "/not/existing/file", NULL);
int r = zproc_run (self);
assert (r == -1);
zproc_destroy (&self);
}
π
Test case #4 β the child aborts itself, and the returncode comes back as -SIGABRT. drama, but make it diagnostic.
{
// Test case #4: child abort itself
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
zproc_set_argsx (self, file, "--verbose", "--abrt", NULL);
zproc_set_stdout (self, NULL);
zproc_set_stderr (self, NULL);
zproc_set_stdin (self, NULL);
int r = zproc_run (self);
zclock_sleep (100); // to let actor start the process
assert (r != -1);
zclock_sleep (100);
zframe_t *frame;
zsock_brecv (zproc_stdout (self), "f", &frame);
assert (zframe_is (frame));
assert (zframe_size (frame) > 0);
zframe_destroy (&frame);
zproc_wait (self, -1);
assert (zproc_returncode (self) == -SIGABRT);
zproc_destroy (&self);
}
π₯ Test case #5 β the big one. never-ending subprocess + a zpoller to read from it. this is the main character energy of the whole example, ngl. note the env var ZSP_MESSAGE set to czmq is great\n, the zpoller_wait with an 800 msec timeout, the zproc_timeout_msec = 10000 patience budget, and that first-frame check against "czmq is great\n" with a length of 14.
{
// Test case #5: use never ending subprocess and poller to read data from it
// Create new zproc instance
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
// join stdout of the process to zeromq socket
// all data will be readable from zproc_stdout socket
zproc_set_stdout (self, NULL);
zlist_t *args = zlist_new ();
zlist_autofree (args);
zlist_append (args, file);
zlist_append (args, "--stdout");
zproc_set_args (self, &args);
zhash_t *env = zhash_new ();
zhash_autofree (env);
zhash_insert (env, "ZSP_MESSAGE", "czmq is great\n");
zproc_set_env (self, &env);
// execute the binary. It runs in own actor, which monitor the process and
// pass data accross pipes and zeromq sockets
if (verbose)
zsys_debug("zproc_test() : launching helper '%s'", file );
zproc_run (self);
zpoller_t *poller = zpoller_new (zproc_stdout (self), NULL);
// kill the binary, it never ends, but the test must:
// termination also flushes the output streams so we can
// read them entirely; note that other process runs in
// parallel to this thread
if (verbose)
zsys_debug("zproc_test() : sleeping 4000 msec to gather some output from helper");
zclock_sleep (4000);
zproc_kill (self, SIGTERM);
zproc_wait (self, -1);
// read the content from zproc_stdout - use zpoller and a loop
bool stdout_read = false;
int64_t zproc_timeout_msec = 10000;
int64_t zproc_test_start_msec = zclock_mono();
int64_t zproc_test_elapsed_msec = 0;
while (!zsys_interrupted) {
void *which = zpoller_wait (poller, 800);
zproc_test_elapsed_msec = zclock_mono() - zproc_test_start_msec;
if (!which) {
if (stdout_read) {
if (verbose)
zsys_debug("zproc_test() : did not get stdout from helper, but we already have some (%" PRIi64 " msec remaining to retry)", (zproc_timeout_msec - zproc_test_elapsed_msec) );
break;
}
if (zproc_timeout_msec > zproc_test_elapsed_msec) {
if (verbose)
zsys_debug("zproc_test() : did not get stdout from helper, %" PRIi64 " msec remaining to retry", (zproc_timeout_msec - zproc_test_elapsed_msec) );
continue;
}
// ...else : we've slept a lot and got no response; kill the helper
if (verbose)
zsys_debug("zproc_test() : did not get stdout from helper, patience expired (%" PRIi64 " msec remaining to retry)", (zproc_timeout_msec - zproc_test_elapsed_msec) );
break;
}
if (which == zproc_stdout (self)) {
// it suffices for us to have read something
// we only check the first frame, others may start with the
// expected key string broken mid-way due to alignment etc.,
// but we drain the whole incoming queue of stdout frames.
zframe_t *frame;
zsock_brecv (zproc_stdout (self), "f", &frame);
assert (frame);
assert (zframe_data (frame));
if (!stdout_read) {
if (verbose)
zsys_debug("zproc_test() : got stdout from helper, %" PRIi64 " msec was remaining to retry", (zproc_timeout_msec - zproc_test_elapsed_msec));
assert (!strncmp(
"czmq is great\n",
(char*) zframe_data (frame),
14));
stdout_read = true;
}
if (verbose)
zframe_print (frame, "zproc_test");
zframe_destroy (&frame);
continue;
}
// should not get there
if (verbose)
zsys_debug("zproc_test() : reached the unreachable point (unexpected zpoller result), %" PRIi64 " msec was remaining to retry", (zproc_timeout_msec - zproc_test_elapsed_msec) );
assert (false);
}
assert (stdout_read);
zpoller_destroy (&poller);
zproc_destroy (&self);
}
β³ Test case #6 β wait for a process that hangs, then kill it. zproc_wait with 1000 gives back ZPROC_RUNNING, then zproc_shutdown does its thing.
{
// testcase #6 wait for process that hangs, kill it
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
zproc_set_argsx (self, file, NULL);
if (verbose)
zsys_debug("zproc_test() : launching helper '%s'", file);
int r = zproc_run (self);
assert (r == 0);
r = zproc_wait (self, 1000);
assert (r == ZPROC_RUNNING);
assert (zproc_running (self));
zproc_shutdown (self, 1000);
assert (!zproc_running (self));
zproc_destroy (&self);
}
β±οΈ Test case #7 β wait for a process that exits earlier. --quit 1, zproc_wait with 8000, and it must come back in under 2000 msec. speed, slay.
{
// testcase #7 wait for process that exits earlier
zproc_t *self = zproc_new ();
assert (self);
zproc_set_verbose (self, verbose);
zproc_set_argsx (self, file, "--quit", "1", NULL);
if (verbose)
zsys_debug("zproc_test() : launching helper '%s' --quit 1", file);
int r = zproc_run (self);
assert (r == 0);
int t = zclock_mono ();
r = zproc_wait (self, 8000);
assert (r == 0);
t = zclock_mono () - t;
assert (t < 2000);
zproc_destroy (&self);
}
π AUTHORS π
The czmq manual was written by the authors in the AUTHORS file. credit where credit is due, periodt. π
π RESOURCES π
Main web site:
Report bugs to the email <zeromq-dev@lists.zeromq.org[1]>
π COPYRIGHT π
Copyright (c) the Contributors as noted in the AUTHORS file. This file is part of CZMQ, the high-level C binding for 0MQ: http://czmq.zeromq.org. This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. LICENSE included with the czmq distribution.
π§ NOTES π§
zeromq-dev@lists.zeromq.orgβmailto:zeromq-dev@lists.zeromq.org
and that's the whole page bestie, we ate and left no crumbs π«Ά this was CZMQ 4.2.1, dated 01/16/2026, and you just read ZPROC(3). go forth and spawn responsibly πβ¨
π¦ where this page comes from
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- fedora 44: czmq-devel 4.2.1-24.fc44 Β· MPL-2.0
- ubuntu resolute: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- ubuntu stonking: libczmq-dev 4.2.1-3build1 Β· MPL-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms