webmcp

annotate framework/env/execute/command.lua @ 489:42ddff7319e0

Avoid blocking of execute.command{...} if child process closes file descriptors but doesn't terminate
author jbe
date Sun Jun 18 01:58:25 2017 +0200 (2017-06-18)
parents 5b1c4f76e44f
children
rev   line source
jbe@486 1 --[[--
jbe@486 2 output, -- collected data from stdout if process exited successfully
jbe@486 3 errmsg = -- error message if execution failed or if process didn't exit successfully
jbe@486 4 execute.command{
jbe@486 5 command = { filename, arg1, arg2, ... }, -- command and arguments
jbe@486 6 stdin_data = stdin_data, -- optional data to be sent to process via stdin
jbe@486 7 stdout_result_handler = stdout_result_handler, -- callback receiving: stdout data, success boolean, optional error message
jbe@486 8 stderr_line_handler = stderr_line_handler, -- callback for processing stderr line-wise
jbe@486 9 exit_handler = exit_handler, -- callback when process exited
jbe@486 10 signal_handler = signal_handler, -- callback when process terminated due to signal
jbe@486 11 timeout_handler = timeout_handler, -- callback when process gets killed due to timeout
jbe@486 12 abort_handler = abort_handler, -- callback when process gets killed due to request by poll function
jbe@486 13 abortable = abortable, -- set to true if process shall be terminated if poll function requests termination
jbe@486 14 poll = poll, -- alternative poll command with moonbridge_io.poll(...) semantics
jbe@486 15 db = db, -- database handle for waiting for notifies
jbe@486 16 db_notify_handler = db_notify_handler -- handler for database notifications which may return true to kill process
jbe@486 17 }
jbe@486 18
jbe@486 19 --]]--
jbe@486 20
jbe@486 21 function execute.command(args)
jbe@486 22
jbe@486 23 local moonbridge_io = require("moonbridge_io")
jbe@486 24 local poll = args.poll or moonbridge_io.poll
jbe@486 25
jbe@486 26 local stdout_chunks, stderr_chunks = {}, {}
jbe@486 27
jbe@486 28 local function return_error(errmsg)
jbe@486 29 if args.stdout_result_handler then
jbe@486 30 args.stdout_result_handler(table.concat(stdout_chunks), false, errmsg)
jbe@486 31 end
jbe@486 32 return nil, errmsg
jbe@486 33 end
jbe@486 34
jbe@487 35 if args.abortable then
jbe@487 36 local pollready, pollmsg, pollterm = poll(nil, nil, 0, true)
jbe@487 37 if pollterm then
jbe@488 38 if args.abort_handler then args.abort_handler(pollmsg) end
jbe@487 39 return_error(pollmsg)
jbe@487 40 end
jbe@487 41 end
jbe@487 42
jbe@487 43 local start = moonbridge_io.timeref()
jbe@487 44 local process, errmsg = moonbridge_io.exec(table.unpack(args.command))
jbe@487 45 if not process then return nil, errmsg end
jbe@487 46
jbe@489 47 local read_fds = {
jbe@489 48 [process] = true,
jbe@489 49 [process.stdout] = true,
jbe@489 50 [process.stderr] = true
jbe@489 51 }
jbe@489 52 local write_fds = {
jbe@489 53 [process.stdin] = true
jbe@489 54 }
jbe@487 55 if args.db then
jbe@487 56 read_fds[args.db.fd] = true
jbe@487 57 end
jbe@487 58
jbe@486 59 local function write(...)
jbe@486 60 if write_fds[process.stdin] then
jbe@486 61 local buffered = process.stdin:flush_nb(...)
jbe@486 62 if not buffered or buffered == 0 then
jbe@486 63 process.stdin:close()
jbe@486 64 write_fds[process.stdin] = nil
jbe@486 65 end
jbe@486 66 end
jbe@486 67 end
jbe@486 68 write(args.stdin_data or "")
jbe@486 69
jbe@489 70 local status
jbe@489 71
jbe@486 72 while
jbe@489 73 not status or
jbe@486 74 read_fds[process.stdout] or read_fds[process.stderr] or
jbe@486 75 write_fds[process.stdin]
jbe@486 76 do
jbe@486 77 local timeout = args.timeout and args.timeout-moonbridge_io.timeref(start)
jbe@487 78 local pollready, pollmsg, pollterm =
jbe@486 79 poll(read_fds, write_fds, timeout, args.abortable)
jbe@487 80 if not pollready then
jbe@489 81 if not status then
jbe@489 82 process:kill():wait()
jbe@489 83 end
jbe@486 84 if pollterm then
jbe@488 85 if args.abort_handler then args.abort_handler(pollmsg) end
jbe@486 86 else
jbe@486 87 if args.timeout_handler then args.timeout_handler() end
jbe@486 88 end
jbe@486 89 return return_error(pollmsg)
jbe@486 90 end
jbe@489 91 if not status then
jbe@489 92 status = process:wait_nb()
jbe@489 93 if status then
jbe@489 94 read_fds[process] = nil
jbe@489 95 end
jbe@489 96 end
jbe@486 97 if args.db then
jbe@486 98 local channel, payload, pid = db:wait(0)
jbe@486 99 if channel then
jbe@486 100 if args.db_notify_handler(channel, payload, pid) then
jbe@486 101 process:kill():wait()
jbe@486 102 return return_error("Database event received")
jbe@486 103 end
jbe@486 104 end
jbe@486 105 end
jbe@486 106 if read_fds[process.stdout] then
jbe@486 107 local chunk, status = process.stdout:read_nb()
jbe@486 108 if not chunk or status == "eof" then
jbe@486 109 process.stdout:close()
jbe@486 110 read_fds[process.stdout] = nil
jbe@486 111 end
jbe@486 112 if chunk and chunk ~= "" then
jbe@486 113 stdout_chunks[#stdout_chunks+1] = chunk
jbe@486 114 end
jbe@486 115 end
jbe@486 116 if read_fds[process.stderr] then
jbe@486 117 local chunk, status = process.stderr:read_nb()
jbe@486 118 if not chunk or status == "eof" then
jbe@486 119 process.stderr:close()
jbe@486 120 read_fds[process.stderr] = nil
jbe@486 121 end
jbe@486 122 if chunk and args.stderr_line_handler then
jbe@486 123 while true do
jbe@486 124 local chunk1, chunk2 = string.match(chunk, "(.-)\n(.*)")
jbe@486 125 if not chunk1 then break end
jbe@486 126 stderr_chunks[#stderr_chunks+1] = chunk1
jbe@486 127 args.stderr_line_handler(table.concat(stderr_chunks))
jbe@486 128 stderr_chunks = {}
jbe@486 129 chunk = chunk2
jbe@486 130 end
jbe@486 131 if chunk ~= "" then
jbe@486 132 stderr_chunks[#stderr_chunks+1] = chunk
jbe@486 133 end
jbe@486 134 if status == "eof" then
jbe@486 135 local line = table.concat(stderr_chunks)
jbe@486 136 if #line > 0 then args.stderr_line_handler(line) end
jbe@486 137 end
jbe@486 138 end
jbe@486 139 end
jbe@486 140 write()
jbe@486 141 end
jbe@486 142
jbe@486 143 if status < 0 then
jbe@486 144 if args.signal_handler then
jbe@486 145 args.signal_handler(-status)
jbe@486 146 end
jbe@486 147 return return_error("Command terminated by signal " .. -status)
jbe@486 148 elseif status > 0 then
jbe@486 149 if args.exit_handler then
jbe@486 150 args.exit_handler(status)
jbe@486 151 end
jbe@486 152 return return_error("Command returned exit code " .. status)
jbe@486 153 elseif args.stdout_result_handler then
jbe@486 154 args.stdout_result_handler(table.concat(stdout_chunks), true)
jbe@486 155 return true
jbe@486 156 else
jbe@486 157 return table.concat(stdout_chunks)
jbe@486 158 end
jbe@486 159
jbe@486 160 end

Impressum / About Us