diff options
Diffstat (limited to 'mod')
-rw-r--r-- | mod/external-handler.cxx | 322 |
1 files changed, 161 insertions, 161 deletions
diff --git a/mod/external-handler.cxx b/mod/external-handler.cxx index d05e5bf..e88e4b4 100644 --- a/mod/external-handler.cxx +++ b/mod/external-handler.cxx @@ -76,209 +76,209 @@ namespace brep string ref (data_dir.leaf ().string ()); for (;;) // Breakout loop. + try + { + fdpipe pipe (fdopen_pipe ()); // Can throw io_error. + + // Redirect the diagnostics to the web server error log. + // + process pr ( + process_start_callback ([&trace] (const char* args[], size_t n) + { + if (trace != nullptr) + *trace << process_args {args, n}; + }, + 0 /* stdin */, + pipe /* stdout */, + 2 /* stderr */, + handler, + args, + data_dir)); + pipe.out.close (); + + auto kill = [&pr, &warn, &handler, &ref] () + { + // We may still end up well (see below), thus this is a warning. + // + warn << "ref " << ref << ": process " << handler + << " execution timeout expired"; + + pr.kill (); + }; + try { - fdpipe pipe (fdopen_pipe ()); // Can throw io_error. + ifdstream is (move (pipe.in), fdstream_mode::non_blocking); - // Redirect the diagnostics to the web server error log. - // - process pr ( - process_start_callback ([&trace] (const char* args[], size_t n) - { - if (trace != nullptr) - *trace << process_args {args, n}; - }, - 0 /* stdin */, - pipe /* stdout */, - 2 /* stderr */, - handler, - args, - data_dir)); - pipe.out.close (); - - auto kill = [&pr, &warn, &handler, &ref] () - { - // We may still end up well (see below), thus this is a warning. - // - warn << "ref " << ref << ": process " << handler - << " execution timeout expired"; - - pr.kill (); - }; + const size_t nbuf (8192); + char buf[nbuf]; - try + while (is.is_open ()) { - ifdstream is (move (pipe.in), fdstream_mode::non_blocking); + time_point start; + milliseconds wd (10); // Max time to wait for the data portion. - const size_t nbuf (8192); - char buf[nbuf]; - - while (is.is_open ()) + if (timeout) { - time_point start; - milliseconds wd (10); // Max time to wait for the data portion. + start = system_clock::now (); - if (timeout) - { - start = system_clock::now (); + if (*timeout < wd) + wd = *timeout; + } - if (*timeout < wd) - wd = *timeout; - } + timeval tm {wd.count () / 1000 /* seconds */, + wd.count () % 1000 * 1000 /* microseconds */}; + + fd_set rd; + FD_ZERO (&rd); + FD_SET (is.fd (), &rd); - timeval tm {wd.count () / 1000 /* seconds */, - wd.count () % 1000 * 1000 /* microseconds */}; + int r (select (is.fd () + 1, &rd, nullptr, nullptr, &tm)); - fd_set rd; - FD_ZERO (&rd); - FD_SET (is.fd (), &rd); + if (r == -1) + { + // Don't fail if the select() call was interrupted by the + // signal. + // + if (errno != EINTR) + throw_system_ios_failure (errno, "select failed"); + } + else if (r != 0) // Is data available? + { + assert (FD_ISSET (is.fd (), &rd)); - int r (select (is.fd () + 1, &rd, nullptr, nullptr, &tm)); + // The only leagal way to read from non-blocking ifdstream. + // + streamsize n (is.readsome (buf, nbuf)); - if (r == -1) + // Close the stream (and bail out) if the end of the data is + // reached. Otherwise cache the read data. + // + if (is.eof ()) + is.close (); + else { - // Don't fail if the select() call was interrupted by the - // signal. + // The data must be available. // - if (errno != EINTR) - throw_system_ios_failure (errno, "select failed"); - } - else if (r != 0) // Is data available? - { - assert (FD_ISSET (is.fd (), &rd)); - - // The only leagal way to read from non-blocking ifdstream. + // Note that we could keep reading until the readsome() call + // returns 0. However, this way we could potentially exceed + // the timeout significantly for some broken handler that + // floods us with data. So instead, we will be checking the + // process execution time after every data chunk read. // - streamsize n (is.readsome (buf, nbuf)); + assert (n != 0); - // Close the stream (and bail out) if the end of the data is - // reached. Otherwise cache the read data. - // - if (is.eof ()) - is.close (); - else - { - // The data must be available. - // - // Note that we could keep reading until the readsome() call - // returns 0. However, this way we could potentially exceed - // the timeout significantly for some broken handler that - // floods us with data. So instead, we will be checking the - // process execution time after every data chunk read. - // - assert (n != 0); - - ss.write (buf, n); - } + ss.write (buf, n); } - else // Timeout occured. + } + else // Timeout occured. + { + // Normally, we don't expect timeout to occur on the pipe read + // operation if the process has terminated successfully, as + // all its output must already be buffered (including eof). + // However, there can be some still running handler's child + // that has inherited the parent's stdout. In this case we + // assume that we have read all the handler's output, close + // the stream, log the warning and bail out. + // + if (pr.exit) { - // Normally, we don't expect timeout to occur on the pipe read - // operation if the process has terminated successfully, as - // all its output must already be buffered (including eof). - // However, there can be some still running handler's child - // that has inherited the parent's stdout. In this case we - // assume that we have read all the handler's output, close - // the stream, log the warning and bail out. + // We keep reading only upon successful handler termination. // - if (pr.exit) - { - // We keep reading only upon successful handler termination. - // - assert (*pr.exit); + assert (*pr.exit); - is.close (); + is.close (); - warn << "ref " << ref << ": process " << handler - << " stdout is not closed after termination (possibly " - << "handler's child still running)"; - } + warn << "ref " << ref << ": process " << handler + << " stdout is not closed after termination (possibly " + << "handler's child still running)"; } + } + + if (timeout) + { + time_point now (system_clock::now ()); + + // Assume we have waited the full amount if the time + // adjustment is detected. + // + duration d (now > start ? now - start : wd); - if (timeout) + // If the timeout is not fully exhausted, then decrement it and + // try to read some more data from the handler' stdout. + // Otherwise, kill the process, if not done yet. + // + // Note that it may happen that we are killing an already + // terminated process, in which case kill() just sets the + // process exit information. On the other hand it's guaranteed + // that the process is terminated after the kill() call, and + // so the pipe is presumably closed on the write end (see + // above for details). Thus, if the process terminated + // successfully, we will continue reading until eof is + // reached or read timeout occurred. Yes, it may happen that + // we will succeed even with the kill. + // + if (*timeout > d) + *timeout -= duration_cast<milliseconds> (d); + else if (!pr.exit) { - time_point now (system_clock::now ()); + kill (); - // Assume we have waited the full amount if the time - // adjustment is detected. - // - duration d (now > start ? now - start : wd); + assert (pr.exit); - // If the timeout is not fully exhausted, then decrement it and - // try to read some more data from the handler' stdout. - // Otherwise, kill the process, if not done yet. + // Close the stream (and bail out) if the process hasn't + // terminate successfully. // - // Note that it may happen that we are killing an already - // terminated process, in which case kill() just sets the - // process exit information. On the other hand it's guaranteed - // that the process is terminated after the kill() call, and - // so the pipe is presumably closed on the write end (see - // above for details). Thus, if the process terminated - // successfully, we will continue reading until eof is - // reached or read timeout occurred. Yes, it may happen that - // we will succeed even with the kill. - // - if (*timeout > d) - *timeout -= duration_cast<milliseconds> (d); - else if (!pr.exit) - { - kill (); - - assert (pr.exit); - - // Close the stream (and bail out) if the process hasn't - // terminate successfully. - // - if (!*pr.exit) - is.close (); - - *timeout = milliseconds::zero (); - } + if (!*pr.exit) + is.close (); + + *timeout = milliseconds::zero (); } } + } - assert (!is.is_open ()); - - if (!timeout) - pr.wait (); + assert (!is.is_open ()); - // If the process is not terminated yet, then wait for its - // termination for the remaining time. Kill it if the timeout has - // been exceeded and the process still hasn't terminate. - // - else if (!pr.exit && !pr.timed_wait (*timeout)) - kill (); + if (!timeout) + pr.wait (); - assert (pr.exit); // The process must finally be terminated. + // If the process is not terminated yet, then wait for its + // termination for the remaining time. Kill it if the timeout has + // been exceeded and the process still hasn't terminate. + // + else if (!pr.exit && !pr.timed_wait (*timeout)) + kill (); - if (*pr.exit) - break; // Get out of the breakout loop. + assert (pr.exit); // The process must finally be terminated. - error << "ref " << ref << ": process " << handler << " " - << *pr.exit; + if (*pr.exit) + break; // Get out of the breakout loop. - // Fall through. - } - catch (const io_error& e) - { - if (pr.wait ()) - error << "ref " << ref << ": unable to read handler's output: " - << e; + error << "ref " << ref << ": process " << handler << " " + << *pr.exit; - // Fall through. - } + // Fall through. + } + catch (const io_error& e) + { + if (pr.wait ()) + error << "ref " << ref << ": unable to read handler's output: " + << e; - return nullopt; + // Fall through. } + + return nullopt; + } // Handle process_error and io_error (both derive from system_error). // - catch (const system_error& e) - { - error << "ref " << ref << ": unable to execute '" << handler - << "': " << e; + catch (const system_error& e) + { + error << "ref " << ref << ": unable to execute '" << handler + << "': " << e; - return nullopt; - } + return nullopt; + } result_manifest r; |