SMACC2
Loading...
Searching...
No Matches
cp_subprocess_executor.hpp
Go to the documentation of this file.
1// Copyright 2024 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <chrono>
18#include <functional>
19#include <mutex>
20#include <string>
21
22#include <rclcpp/rclcpp.hpp>
23#include <smacc2/common.hpp>
24#include <smacc2/component.hpp>
27
28namespace smacc2
29{
30namespace client_core_components
31{
32
37{
39 std::string stdout_output;
40 std::string stderr_output;
42 std::chrono::milliseconds execution_time;
43};
44
59{
60public:
62
63 virtual ~CpSubprocessExecutor() = default;
64
65 void onInitialize() override
66 {
67 if (!initialized_)
68 {
69 RCLCPP_INFO_STREAM(
70 getLogger(), "[" << this->getName() << "] CpSubprocessExecutor initialized");
71 initialized_ = true;
72 }
73 }
74
82 SubprocessResult executeCommand(const std::string & command, int timeout_ms = 30000)
83 {
84 std::lock_guard<std::mutex> lock(execution_mutex_);
85
86 SubprocessResult result;
87 result.exit_code = -1;
88 result.timed_out = false;
89
90 auto start_time = std::chrono::steady_clock::now();
91
92 RCLCPP_DEBUG_STREAM(getLogger(), "[" << this->getName() << "] Executing: " << command);
93
94 // Execute command and capture output
95 std::string full_command = command + " 2>&1";
96 FILE * pipe = popen(full_command.c_str(), "r");
97
98 if (!pipe)
99 {
100 result.stderr_output = "Failed to execute command: popen() failed";
101 RCLCPP_ERROR_STREAM(getLogger(), "[" << this->getName() << "] " << result.stderr_output);
103 return result;
104 }
105
106 // Read output
107 std::array<char, 4096> buffer;
108 std::string output;
109
110 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
111 {
112 output += buffer.data();
113
114 // Check timeout
115 if (timeout_ms > 0)
116 {
117 auto elapsed = std::chrono::steady_clock::now() - start_time;
118 if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() > timeout_ms)
119 {
120 pclose(pipe);
121 result.timed_out = true;
122 result.stderr_output = "Command timed out after " + std::to_string(timeout_ms) + "ms";
123 RCLCPP_WARN_STREAM(getLogger(), "[" << this->getName() << "] " << result.stderr_output);
125 return result;
126 }
127 }
128 }
129
130 int status = pclose(pipe);
131 result.exit_code = WEXITSTATUS(status);
132 result.stdout_output = output;
133
134 auto end_time = std::chrono::steady_clock::now();
135 result.execution_time =
136 std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
137
138 RCLCPP_DEBUG_STREAM(
139 getLogger(), "[" << this->getName() << "] Command completed with exit code "
140 << result.exit_code << " in " << result.execution_time.count() << "ms");
141
142 if (result.exit_code == 0)
143 {
145 }
146 else
147 {
149 }
150
151 return result;
152 }
153
162 void executeCommandAsync(const std::string & command, int timeout_ms = 30000)
163 {
164 std::thread([this, command, timeout_ms]() { this->executeCommand(command, timeout_ms); })
165 .detach();
166 }
167
168 // Signals
169 smacc2::SmaccSignal<void(int exit_code, const std::string & output)> onCommandCompleted_;
170 smacc2::SmaccSignal<void(const std::string & error)> onCommandFailed_;
171
172 // Signal connection helpers
173 template <typename T>
175 void (T::*callback)(int, const std::string &), T * object)
176 {
177 return this->getStateMachine()->createSignalConnection(onCommandCompleted_, callback, object);
178 }
179
180 template <typename T>
182 void (T::*callback)(const std::string &), T * object)
183 {
184 return this->getStateMachine()->createSignalConnection(onCommandFailed_, callback, object);
185 }
186
187private:
190};
191
192} // namespace client_core_components
193} // namespace smacc2
ISmaccStateMachine * getStateMachine()
virtual std::string getName() const
rclcpp::Logger getLogger() const
smacc2::SmaccSignalConnection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)
Generic subprocess execution component for running CLI tools.
smacc2::SmaccSignalConnection onCommandFailed(void(T::*callback)(const std::string &), T *object)
SubprocessResult executeCommand(const std::string &command, int timeout_ms=30000)
Execute a command synchronously.
void executeCommandAsync(const std::string &command, int timeout_ms=30000)
Execute a command asynchronously (fire and forget)
smacc2::SmaccSignal< void(const std::string &error)> onCommandFailed_
smacc2::SmaccSignal< void(int exit_code, const std::string &output)> onCommandCompleted_
smacc2::SmaccSignalConnection onCommandCompleted(void(T::*callback)(int, const std::string &), T *object)
boost::signals2::connection SmaccSignalConnection