SMACC2
Loading...
Searching...
No Matches
smacc_ros_launch_client_2.cpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft 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/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
20#include <errno.h>
21#include <fcntl.h>
22#include <poll.h>
23#include <signal.h>
24#include <stdio.h>
25#include <sys/types.h>
26#include <sys/wait.h>
27#include <unistd.h>
28#include <cstdio>
29#include <cstdlib>
30#include <cstring>
31#include <future>
32#include <iostream>
34#include <thread>
35
36namespace smacc2
37{
38namespace client_bases
39{
40using namespace std::chrono_literals;
41ClRosLaunch2::ClRosLaunch2() : cancellationToken_(false) {}
42
43ClRosLaunch2::ClRosLaunch2(std::string packageName, std::string launchFilename)
44: packageName_(packageName), launchFileName_(launchFilename), cancellationToken_(false)
45{
46}
47
49
51{
52 cancellationToken_.store(false);
53 // Start the launch execution thread
54 this->result_ =
56}
57
59{
60 // Set the cancellation flag
61 cancellationToken_.store(true);
62}
63
64std::future<std::string> ClRosLaunch2::executeRosLaunch(
65 std::string packageName, std::string launchFileName, std::function<bool()> cancelCondition,
66 ClRosLaunch2 * client)
67{
68 return std::async(
69 std::launch::async,
70 [packageName, launchFileName, cancelCondition, client]()
71 {
72 RCLCPP_WARN_STREAM(rclcpp::get_logger("smacc2"), "[ClRosLaunch2] Starting ros launch thread");
73
74 std::stringstream cmd;
75 cmd << "ros2 launch " << packageName << " " << launchFileName;
76 std::array<char, 128> buffer;
77 std::string result;
78
79 auto child = runProcess(cmd.str().c_str());
80
81 if (!child.pipe)
82 {
83 throw std::runtime_error("popen() failed!");
84 }
85 if (client != nullptr)
86 {
87 client->launchPid_ = child.pid;
88 }
89
90 int fd = fileno(child.pipe);
91
92 int flags = fcntl(fd, F_GETFL, 0);
93 fcntl(fd, F_SETFL, flags | O_NONBLOCK);
94
95 bool cancelled = false;
96
97 // while (!cancelCondition())
98 while (!cancelled)
99 {
100 cancelled = cancelCondition();
101 size_t bytesRead = fread(buffer.data(), 1, buffer.size(), /*data*/ child.pipe);
102
103 if (bytesRead > 0)
104 {
105 result.append(buffer.data(), bytesRead);
106 }
107 else if (bytesRead == 0)
108 {
109 // No data available yet
110 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Wait before retrying
111 }
112 else
113 {
114 // Read error
115 RCLCPP_ERROR(rclcpp::get_logger("smacc2"), "Error reading from pipe");
116 break;
117 }
118 }
119
120 rclcpp::sleep_for(2s);
121 if (child.pid > 0)
122 {
123 killGrandchildren(child.pid);
124 rclcpp::sleep_for(2s);
125 }
126
127 int status;
128 pid_t child_pid = child.pid;
129 if (waitpid(child_pid, &status, 0) != -1)
130 {
131 if (WIFEXITED(status))
132 {
133 int exit_status = WEXITSTATUS(status);
134 RCLCPP_INFO(
135 rclcpp::get_logger("smacc2"), "Child process exited with status: %d", exit_status);
136 }
137 else if (WIFSIGNALED(status))
138 {
139 int term_signal = WTERMSIG(status);
140 RCLCPP_WARN(
141 rclcpp::get_logger("smacc2"), "Child process terminated by signal: %d", term_signal);
142 }
143 }
144 else
145 {
146 RCLCPP_ERROR(rclcpp::get_logger("smacc2"), "Error waiting for child process.");
147 }
148
149 pclose(child.pipe);
150 close(child.pipe->_fileno); // Close pipe file descriptor but not processes
151
152 RCLCPP_WARN_STREAM(rclcpp::get_logger("smacc2"), "[ClRosLaunch2] RESULT:\n" << result);
153
154 return result;
155 });
156}
157
160
161ProcessInfo runProcess(const char * command)
162{
163 ProcessInfo info;
164 info.pid = -1; // Initialize PID to -1 (error sentinel)
165 info.pipe = nullptr;
166
167 int pipefd[2]; // File descriptors for the pipe
168
169 if (pipe(pipefd) == -1)
170 {
171 perror("pipe");
172 return info;
173 }
174
175 pid_t pid = fork();
176
177 if (pid == 0)
178 {
179 // Child process
180 close(pipefd[0]); // Close read end in child
181 dup2(pipefd[1], STDOUT_FILENO); // Redirect stdout to pipe
182 close(pipefd[1]); // Close write end in child
183
184 execl("/bin/sh", "/bin/sh", "-c", command, nullptr); // Execute the command
185
186 // execl only returns on error
187 std::cerr << "Error executing command: " << command << std::endl;
188 _exit(1); // Use _exit to skip at exit handlers
189 }
190 else if (pid > 0)
191 {
192 // Parent process
193 close(pipefd[1]); // Close write end in parent
194 info.pid = pid;
195 info.pipe = fdopen(pipefd[0], "r"); // Open read end as text-mode FILE
196 }
197 else
198 {
199 std::cerr << "Error creating child process." << std::endl;
200 }
201
202 return info;
203}
204
206{
207 std::string command = "pgrep -P " + std::to_string(pid);
208 FILE * pipe = popen(command.c_str(), "r");
209
210 if (!pipe)
211 {
212 std::cerr << "Error executing pgrep command." << std::endl;
213 return;
214 }
215
216 char buffer[128];
217 std::string result = "";
218
219 while (fgets(buffer, sizeof(buffer), pipe) != NULL)
220 {
221 result += buffer;
222 }
223
224 pclose(pipe);
225
226 std::istringstream iss(result);
227 pid_t childPid;
228 std::vector<pid_t> childPids;
229
230 while (iss >> childPid)
231 {
232 childPids.push_back(childPid);
233 }
234
235 // Kill child processes before killing the parent process
236 for (const pid_t & child : childPids)
237 {
239 }
240
241 // After killing all children, kill the parent process
242 int res = kill(pid, SIGTERM);
243 if (res == 0)
244 {
245 RCLCPP_FATAL(rclcpp::get_logger("smacc2"), "Killed process %d", pid);
246 }
247}
248
249void killGrandchildren(pid_t originalPid) { killProcessesRecursive(originalPid); }
250} // namespace client_bases
251} // namespace smacc2
static std::future< std::string > executeRosLaunch(std::string packageName, std::string launchFilename, std::function< bool()> cancelCondition, ClRosLaunch2 *client=nullptr)
void killGrandchildren(pid_t originalPid)
ProcessInfo runProcess(const char *command)