[PATCH v3] core: Non-blocking io.popen

Stanislav Zudin szudin at tarantool.org
Mon Jun 17 21:54:59 MSK 2019


Changes behavior of popen:kill().
The new version accepts string names of signals as well
as numeric ones.
The string names are platform independent.

Closes #4031

@TarantoolBot document
Title: Nonblocking fio.popen

handle, err = fio.popen(parameters)

fio.popen starts a process and redirects its input/output.

parameters - a table containing arguments to run a process.
The following arguments are expected:

argv - [mandatory] is a table of argument strings passed to
the new program. By convention, the first of these strings should
contain the filename associated with the file being executed.

environment - [optional] is a table of strings, conventionally of the
form key=value, which are passed as environment to the new program.
If not specified a parent's environment is used.

By default stdin, stdout,stderr of the associated process are
available for writing/reading using object's methods
handle:write() and handle:read().
One can override default behavior to redirect streams to/from file
or to input/output of another process or to the default input/output
of the parent process.

stdin - [optional] overrides the child process's standard input.
stdout - [optional] overrides the child process's standard output.
stderr - [optional] overrides the the child process's standard
error output.
May accept one of the following values:
Handle of the file open with fio.open()
Handle of the standard input/output of another process,
open by fio.popen
A constant defining the parent's STDIN, STDOUT or STDERR.
A constants:
fio.PIPE - Opens a file descriptor available for reading/writing
or redirection to/from another process.
fio.DEVNULL - Makes fio.popen redirect the output to /dev/null.

On success returns an object providing methods for
communication with the running process.
Returns nil if underlying functions calls fail;
in this case the second return value, err, contains a error message.

The object created by fio.popen provides the following methods:
read()
write()
kill()
wait()
status()
stdin()
stdout()
stderr()

number handle:stdin()

Returns handle of the child process's standard input.
The handle is available only if it was created with
fio.PIPE option.
Use this handle to setup a redirection
from file or other process to the input of the associated process.
If handle is unavailable the method returns nil.

number handle:stdout()
number handle:stderr()

Return STDOUT and STDIN of the associated process accordingly.
See handle:stdin() for details.

rc,err = handle:wait(timeout)

The wait() waits for the associated process to terminate.

timeout - an integer specifies number of seconds to wait.
If the requested time has elapsed the method returns false,
the second return value, err, contains a error message.
To distinguish timeout from the the other errors use errno.
If timeout is nil, the method waits infinitely until
the associated process is terminated.
On success function returns true.
If failed, rc is false and err contains a error message.

If the associated process is terminated, one can use the following
methods get the exit status:

rc = handle:status()

returns nil if process is still running
 == 0 if the process exited normally
 error code > 0 if the process terminated with an error
 -signal if the process was killed by a signal

rc, err = handle:kill(sig)

The kill() sends a specified signal to the associated process.
On success the method returns true,
if failed - false and error message.
If the sig is nil the default "SIGTERM" is being sent to the process.
If the signal is unknown then the method fails (with error EINVAL).
The method accepts string names of signal as well as their numeric
values.

rc,src,err = handle:read(buffer,size,timeout)

read stdout & stderr of the process started by fio.popen
Usage:
 read(size) -> str, source, err
 read(buf, size) -> length, source, err
 read(size, timeout) -> str, source, err
 read(buf, size, timeout) -> length, source, err

 timeout - number of seconds to wait (optional)
 source contains id of the stream, fio.STDOUT or fio.STDERR
 err - error message if method has failed or nil on success

rc, err = handle:write(buf[, size])
rc, err = handle:write(opts), where opts are:
  * buf
  * size
  * timeout

Writes specified number of bytes
On success returns number of written bytes.
If failed the rc is nil and err contains an error message.
---
Branch: https://github.com/tarantool/tarantool/tree/stanztt/gh-4031-nonblocking-popen
Issue: https://github.com/tarantool/tarantool/issues/4031

 src/CMakeLists.txt              |   1 +
 src/lua/fio.lua                 |  50 +++--------
 src/lua/init.c                  |   2 +
 src/lua/lua_signal.c            | 155 ++++++++++++++++++++++++++++++++
 src/lua/lua_signal.h            |  45 ++++++++++
 test/app-tap/fio_popen.test.lua |  11 +--
 6 files changed, 220 insertions(+), 44 deletions(-)
 create mode 100644 src/lua/lua_signal.c
 create mode 100644 src/lua/lua_signal.h

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 54ac12106..ef1c066c1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -119,6 +119,7 @@ set (server_sources
      lua/string.c
      lua/buffer.c
      lua/swim.c
+     lua/lua_signal.c
      ${lua_sources}
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
      ${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
diff --git a/src/lua/fio.lua b/src/lua/fio.lua
index 51a485b01..d07cac552 100644
--- a/src/lua/fio.lua
+++ b/src/lua/fio.lua
@@ -3,6 +3,7 @@
 local fio = require('fio')
 local ffi = require('ffi')
 local buffer = require('buffer')
+local signal = require('signal')
 local errno = require('errno')
 
 ffi.cdef[[
@@ -22,43 +23,6 @@ fio.STDERR = 2
 fio.PIPE = -2
 fio.DEVNULL = -3
 
-fio.SIGINT	=	2
-fio.SIGILL	=	4
-fio.SIGABRT	=	6
-fio.SIGFPE	=	8
-fio.SIGSEGV	=	11
-fio.SIGTERM	=	15
-
-fio.SIGHUP	=	1
-fio.SIGQUIT	=	3
-fio.SIGTRAP	=	5
-fio.SIGKILL	=	9
-fio.SIGBUS	=	10
-fio.SIGSYS	=	12
-fio.SIGPIPE	=	13
-fio.SIGALRM	=	14
-
-fio.SIGURG	=	16
-fio.SIGSTOP	=	17
-fio.SIGTSTP	=	18
-fio.SIGCONT	=	19
-fio.SIGCHLD	=	20
-fio.SIGTTIN	=	21
-fio.SIGTTOU	=	22
-fio.SIGPOLL	=	23
-fio.SIGXCPU	=	24
-fio.SIGXFSZ	=	25
-fio.SIGVTALRM=	26
-fio.SIGPROF	=	27
-fio.SIGUSR1	=	30
-fio.SIGUSR2	=	31
-
-fio.SIGWINCH=	28
-
-fio.SIGIO	=	fio.SIGPOLL
-fio.SIGIOT	=	fio.SIGABRT
-fio.SIGCLD	=	fio.SIGCHLD
-
 
 local function sprintf(fmt, ...)
     if select('#', ...) == 0 then
@@ -367,9 +331,17 @@ popen_methods.kill = function(self, sig)
     end
 
     if sig == nil then
-        sig = fio.SIGTERM
+        sig = 'SIGTERM'
+    end
+    if type(sig) == 'string' then
+        sig = signal.c.signals[sig]
+        if sig == nil then
+            errno(errno.EINVAL)
+            return false, sprintf("fio.popen.kill(): unknown signal: %s", sig)
+        end
+    else
+        sig = tonumber(sig)
     end
-    sig = tonumber(sig)
 
     return internal.popen_kill(self.fh, sig)
 end
diff --git a/src/lua/init.c b/src/lua/init.c
index 9982828d9..0f0e9f0fc 100644
--- a/src/lua/init.c
+++ b/src/lua/init.c
@@ -56,6 +56,7 @@
 #include "lua/msgpack.h"
 #include "lua/pickle.h"
 #include "lua/fio.h"
+#include "lua/lua_signal.h"
 #include "lua/httpc.h"
 #include "lua/utf8.h"
 #include "lua/swim.h"
@@ -449,6 +450,7 @@ tarantool_lua_init(const char *tarantool_bin, int argc, char **argv)
 	tarantool_lua_fiber_channel_init(L);
 	tarantool_lua_errno_init(L);
 	tarantool_lua_error_init(L);
+	tarantool_lua_signal_init(L);
 	tarantool_lua_fio_init(L);
 	tarantool_lua_socket_init(L);
 	tarantool_lua_pickle_init(L);
diff --git a/src/lua/lua_signal.c b/src/lua/lua_signal.c
new file mode 100644
index 000000000..67c751073
--- /dev/null
+++ b/src/lua/lua_signal.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "lua/lua_signal.h"
+#include <sys/types.h>
+#include <signal.h>
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "lua/utils.h"
+
+#ifndef PUSHTABLE
+#define PUSHTABLE(name, method, value)	{	\
+	lua_pushliteral(L, name);		\
+	method(L, value);			\
+	lua_settable(L, -3);			\
+}
+#endif /*PUSHTABLE*/
+
+void
+tarantool_lua_signal_init(struct lua_State *L)
+{
+	static const struct luaL_Reg signal_methods[] = {
+		{ NULL,	NULL }
+	};
+
+	luaL_register_module(L, "signal", signal_methods);
+
+	lua_pushliteral(L, "c");
+	lua_newtable(L);
+
+	lua_pushliteral(L, "signals");
+	lua_newtable(L);
+
+#ifdef SIGINT
+	PUSHTABLE("SIGINT", lua_pushinteger, SIGINT);
+#endif
+#ifdef SIGILL
+	PUSHTABLE("SIGILL", lua_pushinteger, SIGILL);
+#endif
+#ifdef SIGABRT
+	PUSHTABLE("SIGABRT", lua_pushinteger, SIGABRT);
+#endif
+#ifdef SIGFPE
+	PUSHTABLE("SIGFPE", lua_pushinteger, SIGFPE);
+#endif
+#ifdef SIGSEGV
+	PUSHTABLE("SIGSEGV", lua_pushinteger, SIGSEGV);
+#endif
+#ifdef SIGTERM
+	PUSHTABLE("SIGTERM", lua_pushinteger, SIGTERM);
+#endif
+
+#ifdef SIGHUP
+	PUSHTABLE("SIGHUP", lua_pushinteger, SIGHUP);
+#endif
+#ifdef SIGQUIT
+	PUSHTABLE("SIGQUIT", lua_pushinteger, SIGQUIT);
+#endif
+#ifdef SIGTRAP
+	PUSHTABLE("SIGTRAP", lua_pushinteger, SIGTRAP);
+#endif
+#ifdef SIGKILL
+	PUSHTABLE("SIGKILL", lua_pushinteger, SIGKILL);
+#endif
+#ifdef SIGBUS
+	PUSHTABLE("SIGBUS", lua_pushinteger, SIGBUS);
+#endif
+#ifdef SIGSYS
+	PUSHTABLE("SIGSYS", lua_pushinteger, SIGSYS);
+#endif
+#ifdef SIGPIPE
+	PUSHTABLE("SIGPIPE", lua_pushinteger, SIGPIPE);
+#endif
+#ifdef SIGALRM
+	PUSHTABLE("SIGALRM", lua_pushinteger, SIGALRM);
+#endif
+
+#ifdef SIGURG
+	PUSHTABLE("SIGURG", lua_pushinteger, SIGURG);
+#endif
+#ifdef SIGSTOP
+	PUSHTABLE("SIGSTOP", lua_pushinteger, SIGSTOP);
+#endif
+#ifdef SIGTSTP
+	PUSHTABLE("SIGTSTP", lua_pushinteger, SIGTSTP);
+#endif
+#ifdef SIGCONT
+	PUSHTABLE("SIGCONT", lua_pushinteger, SIGCONT);
+#endif
+#ifdef SIGCHLD
+	PUSHTABLE("SIGCHLD", lua_pushinteger, SIGCHLD);
+#endif
+#ifdef SIGTTIN
+	PUSHTABLE("SIGTTIN", lua_pushinteger, SIGTTIN);
+#endif
+#ifdef SIGTTOU
+	PUSHTABLE("SIGTTOU", lua_pushinteger, SIGTTOU);
+#endif
+#ifdef SIGPOLL
+	PUSHTABLE("SIGPOLL", lua_pushinteger, SIGPOLL);
+#endif
+#ifdef SIGXCPU
+	PUSHTABLE("SIGXCPU", lua_pushinteger, SIGXCPU);
+#endif
+#ifdef SIGXFSZ
+	PUSHTABLE("SIGXFSZ", lua_pushinteger, SIGXFSZ);
+#endif
+#ifdef SIGVTALRM
+	PUSHTABLE("SIGVTALRM", lua_pushinteger, SIGVTALRM);
+#endif
+#ifdef SIGPROF
+	PUSHTABLE("SIGPROF", lua_pushinteger, SIGPROF);
+#endif
+#ifdef SIGUSR1
+	PUSHTABLE("SIGUSR1", lua_pushinteger, SIGUSR1);
+#endif
+#ifdef SIGUSR2
+	PUSHTABLE("SIGUSR2", lua_pushinteger, SIGUSR2);
+#endif
+	lua_settable(L, -3); /* "signals" */
+
+	lua_settable(L, -3); /* "c" */
+	lua_pop(L, 1);
+}
diff --git a/src/lua/lua_signal.h b/src/lua/lua_signal.h
new file mode 100644
index 000000000..814692077
--- /dev/null
+++ b/src/lua/lua_signal.h
@@ -0,0 +1,45 @@
+#ifndef INCLUDES_TARANTOOL_LUA_SIGNAL_H
+#define INCLUDES_TARANTOOL_LUA_SIGNAL_H
+
+/*
+ * Copyright 2010-2019, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+void tarantool_lua_signal_init(struct lua_State *L);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /*INCLUDES_TARANTOOL_LUA_SIGNAL_H*/
diff --git a/test/app-tap/fio_popen.test.lua b/test/app-tap/fio_popen.test.lua
index c26673a35..c91b7f5d2 100755
--- a/test/app-tap/fio_popen.test.lua
+++ b/test/app-tap/fio_popen.test.lua
@@ -3,6 +3,7 @@
 local tap = require('tap')
 local fio = require('fio')
 local errno = require('errno')
+local signal = require('signal')
 local fiber = require('fiber')
 local test = tap.test()
 
@@ -39,7 +40,7 @@ rc,err = app1:wait(5)
 test:is(rc, true, "#1. Process was killed")
 
 rc = app1:status()
-test:is(rc, -15, "#1. Process was killed 2")
+test:is(rc, -signal.c.signals['SIGTERM'], "#1. Process was killed 2")
 
 rc,src,err = app1:read(128,0.1)
 test:is(rc, nil, "#1. Cant read from the dead process")
@@ -80,7 +81,7 @@ rc,err = app1:wait()
 test:is(rc, true, "#2. Process is terminated")
 
 rc = app1:status()
-test:is(rc, -15, "#2. Process was killed")
+test:is(rc, -signal.c.signals['SIGTERM'], "#2. Process was killed")
 
 app1 = nil
 
@@ -113,13 +114,13 @@ test:is(err, nil, "#3. Reading ok")
 test:is(src, fio.STDOUT, "#3. Read from STDOUT")
 test:is(rc, test2str, "#3. Received exact string")
 
-rc,err = app1:kill(fio.SIGHUP)
+rc,err = app1:kill('SIGHUP')
 test:is(rc, true, "#3. Sending kill(1)")
 
 rc,err = app1:wait()
 test:is(rc, true, "#3. Process was killed")
 rc = app1:status()
-test:is(rc, -1, "#3. Process was killed")
+test:is(rc, -signal.c.signals['SIGHUP'], "#3. Process was killed")
 
 app1 = nil
 
@@ -246,7 +247,7 @@ rc,err = app1:wait()
 test:is(rc, true, "#6. Process is terminated")
 
 rc = app1:status()
-test:is(rc, -15, "#6. Process was killed")
+test:is(rc, -signal.c.signals['SIGTERM'], "#6. Process was killed")
 
 app1 = nil
 
-- 
2.17.1




More information about the Tarantool-patches mailing list