I am trying to make an application using python code and C++, but I need some kind of protection against infinite loops or too long executions. I tried following some threads and seeing how other people solved their issues, but couldn't find a working solution. I have this example code:
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <atomic>
#include <chrono>
#include <cstdlib>
#include "pybind11/pybind11.h"
#include "pybind11/embed.h"
namespace py = pybind11;
std::atomic<bool> stopped(false);
std::atomic<bool> executed(false);
void python_executor(const std::string &code)
{
py::gil_scoped_acquire acquire;
try
{
std::cout << "+ Executing python script..." << std::endl;
py::exec(code);
std::cout << "+ Finished python script" << std::endl;
}
catch (const std::exception &e)
{
std::cout << "@ " << e.what() << std::endl;
}
executed = true;
std::cout << "+ Terminated normal" << std::endl;
}
int main()
{
std::cout << "+ Starting..." << std::endl;
std::string code = R"(
# infinite_writer.py
import time
file_path = r"C:\Temp\loop.txt"
counter = 1
while True:
with open(file_path, "a") as f:
f.write(f"Line {counter}\n")
counter += 1
time.sleep(1) # optional: wait 1 second between writes
)";
py::scoped_interpreter interpreterGuard{};
py::gil_scoped_release release;
std::thread th(python_executor, code);
auto threadId = th.get_id();
std::cout << "+ Thread: " << threadId << std::endl;
// stopped = true;
int maxExecutionTime = 10;
auto start = std::chrono::steady_clock::now();
while (!executed)
{
auto elapsed = std::chrono::steady_clock::now() - start;
if (elapsed > std::chrono::seconds(maxExecutionTime)) {
std::cout << "Interrupting...";
PyErr_SetInterrupt();
std::this_thread::sleep_for(std::chrono::seconds(1));
if (th.joinable()) {
th.join();
executed = true;
break;
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "+ Waiting..." << std::endl;
}
// Make sure to join the thread if it's still running
if (th.joinable()) {
th.join();
}
std::cout << "+ Finished" << std::endl;
return EXIT_SUCCESS;
}
It's purposely an infinite loop in python, to try and stop it when timeout hits, but it never finishes, I got the following console response:
+ Starting...
+ Thread: 28596
+ Executing python script...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
+ Waiting...
Interrupting...
It stays at this part infinitely, proving that it didn't finish execution of the python code. What can I do to correctly end the execution?
I tried exactly like the example code, with PyErr_SetInterrupt, and also tried using PyEval_SetTrace lie said in this thread on github: https://github.com/pybind/pybind11/issues/2677 with the same result, code still runs after trying to stop it.
You can isolate user's Python code execution in a child process, and after timeout, send signal to it.
Fork child process
Start executing user's Python code in it
In main process, start a thread that will send a signal to that child process when the time is over
In main process, wait for the child to finish (normally or by signal)
Example for Unix-like OS:
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <csignal>
#include <vector>
#include <sys/types.h>
#include <sys/wait.h>
std::vector<char*> vecstr2vecc(const std::vector<std::string>& str_vec)
{
std::vector<char*> res(str_vec.size() + 1, nullptr);
for (size_t i = 0; i < str_vec.size(); i++)
res[i] = const_cast<char*>(str_vec[i].c_str());
return res;
}
void task(const std::string& code, size_t maxExecutionTime)
{
pid_t pid = fork();
if (pid) // parent
{
std::cout << "Started task with pid=" << pid << std::endl;
std::thread task_timeout_stopper([pid, maxExecutionTime]() {
std::this_thread::sleep_for(std::chrono::seconds(maxExecutionTime));
if (kill(pid, SIGINT) == 0)
{
std::cout << "Sent signal to pid=" << pid << std::endl;
}
});
task_timeout_stopper.detach();
waitpid(pid, NULL, 0);
std::cout << "pid=" << pid << " finished." << std::endl;
}
else // child
{
std::vector<std::string> args = { "python", "-c", code };
execvp(args.front().c_str(), vecstr2vecc(args).data());
}
}
int main()
{
std::string code = R"(
import time
file_path = r"loop.txt"
counter = 1
while counter < 10: # execute for 10 seconds
with open(file_path, "a") as f:
f.write(f"Line {counter}\n")
counter += 1
time.sleep(1) # optional: wait 1 second between writes
)";
task(code, 15); // this task will end in 10 seconds, before timeout of 15
task(code, 5); // this task will be interrupted after 5 seconds
return 0;
}
This gives me the following output:
Started task with pid=120700
pid=120700 finished.
Started task with pid=120737
Sent signal to pid=120737
Traceback (most recent call last):
File "<string>", line 12, in <module>
KeyboardInterrupt
pid=120737 finished.
The results in loop.txt file are as expected:
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 1
Line 2
Line 3
Line 4
Line 5
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With