/// Forks and executes a test case cleanup routine asynchronously. /// /// \param test_program The container test program. /// \param test_case_name The name of the test case to run. /// \param user_config User-provided configuration variables. /// \param body_handle The exit handle of the test case's corresponding /// body. The cleanup will be executed in the same context. /// \param body_result The result of the test case's corresponding body. /// /// \return A handle for the background operation. Used to match the result /// of the execution returned by wait_any() with this invocation. executor::exec_handle spawn_cleanup(const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config, const executor::exit_handle& body_handle, const model::test_result& body_result) { generic.check_interrupt(); const std::shared_ptr< scheduler::interface > interface = find_interface(test_program->interface_name()); LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() % test_case_name); const executor::exec_handle handle = generic.spawn_followup( run_test_cleanup(interface, test_program, test_case_name, user_config), body_handle, cleanup_timeout); const exec_data_ptr data(new cleanup_exec_data( test_program, test_case_name, body_handle, body_result)); all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); return handle; }
/// Cleans up the executor state. void cleanup(void) { PRE(!cleaned); for (exec_data_map::const_iterator iter = all_exec_data.begin(); iter != all_exec_data.end(); ++iter) { const exec_handle& pid = (*iter).first; const exec_data_ptr& data = (*iter).second; process::terminate_group(pid); int status; if (::waitpid(pid, &status, 0) == -1) { // Should not happen. LW(F("Failed to wait for PID %s") % pid); } try { fs::rm_r(data->control_directory); } catch (const fs::error& e) { LE(F("Failed to clean up subprocess work directory %s: %s") % data->control_directory % e.what()); } } all_exec_data.clear(); try { // The following only causes the work directory to be deleted, not // any of its contents, so we expect this to always succeed. This // *should* be sufficient because, in the loop above, we have // individually wiped the subdirectories of any still-unclean // subprocesses. root_work_directory->cleanup(); } catch (const fs::error& e) { LE(F("Failed to clean up executor work directory %s: %s; this is " "an internal error") % root_work_directory->directory() % e.what()); } root_work_directory.reset(NULL); interrupts_handler->unprogram(); interrupts_handler.reset(NULL); }
/// Finds any pending exec_datas that correspond to tests needing cleanup. /// /// \return The collection of test_exec_data objects that have their /// needs_cleanup property set to true. test_exec_data_vector tests_needing_cleanup(void) { test_exec_data_vector tests_data; for (exec_data_map::const_iterator iter = all_exec_data.begin(); iter != all_exec_data.end(); ++iter) { const exec_data_ptr data = (*iter).second; try { test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *data.get()); if (test_data->needs_cleanup) { tests_data.push_back(test_data); test_data->needs_cleanup = false; } } catch (const std::bad_cast& e) { // Do nothing for cleanup_exec_data objects. } } return tests_data; }
/// Common code to run after any of the wait calls. /// /// \param handle The exec_handle of the terminated subprocess. /// \param status The exit status of the terminated subprocess. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle post_wait(const executor::exec_handle handle, const process::status& status) { PRE(handle == status.dead_pid()); LI(F("Waited for subprocess with exec_handle %s") % handle); process::terminate_group(status.dead_pid()); const exec_data_map::iterator iter = all_exec_data.find(handle); exec_data_ptr& data = (*iter).second; data->timer.unprogram(); // It is tempting to assert here (and old code did) that, if the timer // has fired, the process has been forcibly killed by us. This is not // always the case though: for short-lived processes and with very short // timeouts (think 1ms), it is possible for scheduling decisions to // allow the subprocess to finish while at the same time cause the timer // to fire. So we do not assert this any longer and just rely on the // timer expiration to check if the process timed out or not. If the // process did finish but the timer expired... oh well, we do not detect // this correctly but we don't care because this should not really // happen. if (!fs::exists(data->stdout_file)) { std::ofstream new_stdout(data->stdout_file.c_str()); } if (!fs::exists(data->stderr_file)) { std::ofstream new_stderr(data->stderr_file.c_str()); } return exit_handle(std::shared_ptr< exit_handle::impl >( new exit_handle::impl( handle, data->timer.fired() ? none : utils::make_optional(status), data->unprivileged_user, data->start_time, datetime::timestamp::now(), data->control_directory, data->stdout_file, data->stderr_file, data->state_owners, all_exec_data))); }