具有可变参数大小的函数:如何有条件地设置一些参数?

时间:2017-12-04 10:03:03

标签: c++ boost

要使用输出重定向创建boost :: process,您应该执行以下操作:

bp::ipstream out;
bp::child c("c++filt", std_out > out);

现在,有条件地控制重定向的语法是什么:

bool redirect = true; // or false
bp::ipstream out;
bp::child c("c++filt", (redirect) ? std_out > out : "what should I put here??" );

2 个答案:

答案 0 :(得分:2)

花哨的boost::process API围绕提供实现handleron_setupon_error的{​​{1}}个对象(可能还有一些,取决于当前的操作系统)方法这将在某个内部流程启动器的上下文中的流程构造调用中执行,并且将能够改变流程启动器行为。 on_success是一个重载运算符,它将返回这样的处理程序。有关详细信息,请访问boost process Extensions documentation

因此,有条件地控制重定向和其他参数的通用方法是编写通用处理程序代理,接受可选的实际处理程序并调用实际处理程序的适当方法:

std_out > out

答案 1 :(得分:0)

我去过那儿。

事实上,我使用的是启动器功能(不完全是工厂,但是可组合的程序包装器)。

简介

我们有一个带有遗留实现的CommandRunner,我重写为使用Boost。我正在跳过公共界面:

class CommandRunner {
    public: struct IRunnerImpl;
};

实现是Pimpl-ed,并与基本实现一起使用,主要存储简单的与实现无关的参数:

struct CommandRunner::IRunnerImpl {
    virtual ~IRunnerImpl() = default;

    virtual void run()                    = 0;
    virtual void ensure_completed()       = 0;
    virtual std::string to_string() const = 0;

    friend class CommandRunner;
  protected:
    std::string            _working_directory;
    mylibrary::optional<mylibrary::io::time::duration> _time_constraint;
    std::string            _stdin_data;
    int                    _redirected_output_fd     = -1;
    std::string            _redirected_output_fname;
    bool                   _discard_output           = false;
    int                    _exit_code;
    std::string            _stdout_str;
    std::string            _stderr_str;
    bool                   _command_timed_out        = false;
    bool                   _sensitive_args           = false;
    string_map_t           _env;
};

Boost RunnerImpl

ensure_completed的核心是使用这样的助手lambdas组成的:

try {
    mylibrary::threads::safe_io_service safe_ios;
    boost::asio::io_service& ios = safe_ios;
    mylibrary::io::time::timer deadline(ios);

    bp::group process_group;

    bp::async_pipe input(ios);
    std::future<std::string> output, error;

    if (_working_directory.empty())
        _working_directory = ".";

    auto on_exit = [this, &deadline](int exit, std::error_code ec) {
        if (!_command_timed_out) {
            _exit_code = exit;
        }
        deadline.cancel();
        if (ec) s_logger.log(LOG_WARNING) << "Child process returned " << ec.message();
        else    s_logger.log(LOG_DEBUG)   << "Child process returned";
    };

    auto launcher = [](auto&&... args) { return bp::child(std::forward<decltype(args)>(args).../*, bp::posix::fd.restrict_inherit()*/); };

    auto redirect_out = [&](auto f) {
        return [&,f](auto&&... args) {
            if (_discard_output) {
                if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
                    s_logger.log(LOG_ERR) << "Ignoring output redirection with set_discard_output. This is a bug.";
                }
                return f(std::forward<decltype(args)>(args)..., bp::std_out > bp::null, bp::std_err > bp::null);
            }

            if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
                s_logger.log(LOG_WARNING) << "Conflicting output redirection, ignoring filename with descriptor";
            }

            if (_redirected_output_fd != -1) {
                return f(std::forward<decltype(args)>(args)..., bp::posix::fd.bind(1, _redirected_output_fd), bp::std_err > error);
            }

            return _redirected_output_fname.empty()
                ? f(std::forward<decltype(args)>(args)..., bp::std_out > output,                   bp::std_err > error)
                : f(std::forward<decltype(args)>(args)..., bp::std_out > _redirected_output_fname, bp::std_err > error);
        };
    };

    bp::environment bp_env = boost::this_process::environment();
    for (auto& p : _env)
        bp_env[p.first] = p.second;

    auto c = redirect_out(launcher)(_command_path, _args,
            process_group,
            bp::std_in < input,
            bp::start_dir(_working_directory),
            bp_env,
            ios, bp::on_exit(on_exit)
        );

    if (_time_constraint) {
        deadline.expires_from_now(*_time_constraint);
        deadline.async_wait([&](boost::system::error_code ec) {
            if (ec != boost::asio::error::operation_aborted) {
                if (ec) {
                    s_logger.log(LOG_WARNING) << "Unexpected condition in CommandRunner deadline: " << ec.message();
                }

                _command_timed_out = true;
                _exit_code = 1;
                ::killpg(process_group.native_handle(), SIGTERM);

                deadline.expires_from_now(3s); // grace time
                deadline.async_wait([&](boost::system::error_code ec) { if (!ec) process_group.terminate(); }); // timed out
            }
        });
    }

    boost::asio::async_write(input, bp::buffer(_stdin_data), [&input](auto ec, auto bytes_written){
        if (ec) {
            s_logger.log(LOG_WARNING) << "Standard input rejected: " << ec.message() << " after " << bytes_written << " bytes written";
        }
        may_fail([&] { input.close(); });
    });

    ios.run();

    if (output.valid()) _stdout_str = output.get();
    if (error.valid())  _stderr_str = error.get();

    // make sure no grand children survive
    if (process_group && process_group.joinable() && !process_group.wait_for(1s))
        process_group.terminate();

    // Note: even kills get the child reaped; 'on_exit' handler is
    // actually the 'on wait_pid() complete'). No need for c.wait()
    // in this scenario
    //// may_fail([&] { if (c.running()) c.wait(); }); // to avoid zombies
} catch(bp::process_error const& e) {
    if (e.code().value() != static_cast<int>(std::errc::no_child_process)) throw;
}

完全自包含的列表

这个编译,但没有任何公共接口。仅用于说明目的。

注意:

  • 这没有投入生产,因为在某些情况下,Boost实施会陷入异步IO操作。原因尚不清楚,但我怀疑这是epoll is fundamentally broken与我们的用例“疯狂”多线程和分叉相关的毛茸茸案例。
  • 这不包括我们针对safe_io_service尝试的解决方法(保证所有活动的io_services上的fork同步和通知)
  • 这不包括添加受限FD继承的补丁:

    auto launcher = [](auto&&... args) { return 
         bp::child(std::forward<decltype(args)>(args)..., 
         bp::posix::fd.restrict_inherit()); };
    
  • 此处未显示其他关键事项,如全局(库)状态的on-fork处理程序(它们使用pthread_atfork和类似的)

<强> Compiling On Coliru

#include <boost/process.hpp>
#include <boost/process/extend.hpp>
#include <boost/process/async.hpp>
#include <boost/process/posix.hpp>
#include <boost/optional.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <signal.h> // ::killpg
#include <iomanip>
#include <map>

///////
// mockups for standalone exposition
//#include "log.h"
enum LOG_LEVELS{LOG_DEBUG, LOG_WARNING, LOG_ERR};

struct DummyLogger {
    struct Tx {
        template <typename...Ts> friend Tx operator<<(Tx const&, Ts&&...) { return {}; }
    };
    Tx log(LOG_LEVELS) { return {}; }
} s_logger;

//#include "safe_io_service.h"
namespace mylibrary { 
    namespace threads {
        //this did manage fork notifications
        using safe_io_service = boost::asio::io_service;
    } 
    using boost::optional;
    namespace io { namespace time {
            using clock = std::chrono::high_resolution_clock;
            using duration = clock::duration;
            using timer = boost::asio::high_resolution_timer;
        }
    }
}
using namespace std::chrono_literals;

using string_vector_t = std::vector<std::string>;
using string_map_t = std::map<std::string, std::string>;

class CommandRunner {
    public: struct IRunnerImpl;
};

struct CommandRunner::IRunnerImpl {
    virtual ~IRunnerImpl() = default;

    virtual void run()                    = 0;
    virtual void ensure_completed()       = 0;
    virtual std::string to_string() const = 0;

    friend class CommandRunner;
  protected:
    std::string            _working_directory;
    mylibrary::optional<mylibrary::io::time::duration> _time_constraint;
    std::string            _stdin_data;
    int                    _redirected_output_fd     = -1;
    std::string            _redirected_output_fname;
    bool                   _discard_output           = false;
    int                    _exit_code;
    std::string            _stdout_str;
    std::string            _stderr_str;
    bool                   _command_timed_out        = false;
    bool                   _sensitive_args           = false;
    string_map_t           _env;
};

//
//////////////////////////////////////

namespace {

    namespace bp = boost::process;

    template <typename F> static auto may_fail(F&& f) {
        try {
            return std::forward<F>(f)();
        } catch(std::exception const& e) {
            s_logger.log(LOG_DEBUG) << "Ignoring non-fatal error (" << e.what() << ")";
        } catch(...) {
            s_logger.log(LOG_DEBUG) << "Ignoring non-fatal, unspecified error";
        }
    }

}

namespace mylibrary { namespace process { namespace with_boost {

    struct BoostRunnerImpl : CommandRunner::IRunnerImpl {

        BoostRunnerImpl(std::string cmd_path, string_vector_t args) :
            _command_path(std::move(cmd_path)),
            _args(std::move(args))
        {
        }

        std::string _command_path;
        string_vector_t _args;

        virtual void run() override {
            if (_completed) {
                s_logger.log(LOG_DEBUG) << "NOT running already completed command: " << *this;
                return;
            }
            ensure_completed();
        }

        ////////////////////////////
        // implementation
        virtual void ensure_completed() override {
            if (_completed) return;

            // Log command and args
            s_logger.log(LOG_DEBUG) << "Running command: " << *this;

            try {
                try {
                    mylibrary::threads::safe_io_service safe_ios;
                    boost::asio::io_service& ios = safe_ios;
                    mylibrary::io::time::timer deadline(ios);

                    bp::group process_group;

                    bp::async_pipe input(ios);
                    std::future<std::string> output, error;

                    if (_working_directory.empty())
                        _working_directory = ".";

                    auto on_exit = [this, &deadline](int exit, std::error_code ec) {
                        if (!_command_timed_out) {
                            _exit_code = exit;
                        }
                        deadline.cancel();
                        if (ec) s_logger.log(LOG_WARNING) << "Child process returned " << ec.message();
                        else    s_logger.log(LOG_DEBUG)   << "Child process returned";
                    };

                    auto launcher = [](auto&&... args) { return bp::child(std::forward<decltype(args)>(args).../*, bp::posix::fd.restrict_inherit()*/); };

                    auto redirect_out = [&](auto f) {
                        return [&,f](auto&&... args) {
                            if (_discard_output) {
                                if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
                                    s_logger.log(LOG_ERR) << "Ignoring output redirection with set_discard_output. This is a bug.";
                                }
                                return f(std::forward<decltype(args)>(args)..., bp::std_out > bp::null, bp::std_err > bp::null);
                            }

                            if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
                                s_logger.log(LOG_WARNING) << "Conflicting output redirection, ignoring filename with descriptor";
                            }

                            if (_redirected_output_fd != -1) {
                                return f(std::forward<decltype(args)>(args)..., bp::posix::fd.bind(1, _redirected_output_fd), bp::std_err > error);
                            }

                            return _redirected_output_fname.empty()
                                ? f(std::forward<decltype(args)>(args)..., bp::std_out > output,                   bp::std_err > error)
                                : f(std::forward<decltype(args)>(args)..., bp::std_out > _redirected_output_fname, bp::std_err > error);
                        };
                    };

                    bp::environment bp_env = boost::this_process::environment();
                    for (auto& p : _env)
                        bp_env[p.first] = p.second;

                    auto c = redirect_out(launcher)(_command_path, _args,
                            process_group,
                            bp::std_in < input,
                            bp::start_dir(_working_directory),
                            bp_env,
                            ios, bp::on_exit(on_exit)
                        );

                    if (_time_constraint) {
                        deadline.expires_from_now(*_time_constraint);
                        deadline.async_wait([&](boost::system::error_code ec) {
                            if (ec != boost::asio::error::operation_aborted) {
                                if (ec) {
                                    s_logger.log(LOG_WARNING) << "Unexpected condition in CommandRunner deadline: " << ec.message();
                                }

                                _command_timed_out = true;
                                _exit_code = 1;
                                ::killpg(process_group.native_handle(), SIGTERM);

                                deadline.expires_from_now(3s); // grace time
                                deadline.async_wait([&](boost::system::error_code ec) { if (!ec) process_group.terminate(); }); // timed out
                            }
                        });
                    }

                    boost::asio::async_write(input, bp::buffer(_stdin_data), [&input](auto ec, auto bytes_written){
                        if (ec) {
                            s_logger.log(LOG_WARNING) << "Standard input rejected: " << ec.message() << " after " << bytes_written << " bytes written";
                        }
                        may_fail([&] { input.close(); });
                    });

                    ios.run();

                    if (output.valid()) _stdout_str = output.get();
                    if (error.valid())  _stderr_str = error.get();

                    // make sure no grand children survive
                    if (process_group && process_group.joinable() && !process_group.wait_for(1s))
                        process_group.terminate();

                    // Note: even kills get the child reaped; 'on_exit' handler is
                    // actually the 'on wait_pid() complete'). No need for c.wait()
                    // in this scenario
                    //// may_fail([&] { if (c.running()) c.wait(); }); // to avoid zombies
                } catch(bp::process_error const& e) {
                    if (e.code().value() != static_cast<int>(std::errc::no_child_process)) throw;
                }
            } catch(std::exception const& e) {
                s_logger.log(LOG_ERR) << "CommandRunner: " << e.what();
                _completed = true;
                _exit_code = 1;
                throw;
            } catch(...) {
                s_logger.log(LOG_ERR) << "CommandRunner: unexpected error";
                _completed = true;
                _exit_code = 1;
                throw;
            }

            _completed = true;
        }

      private:
        bool _completed = false;

        friend std::ostream& operator<<(std::ostream& os, BoostRunnerImpl const& i) {
            os << i._command_path;
            if (i._sensitive_args)
                os << " (" << i._args.size() << " args)";
            else for (auto& arg : i._args)
                os << " " << std::quoted(arg);
            return os;
        }

        virtual std::string to_string() const override {
            std::ostringstream oss;
            oss << *this;
            return oss.str();
        }
    };

} } }

int main(){}