|
1 | 1 | #include "include/py_connection.h" |
2 | 2 |
|
3 | 3 | #include <algorithm> |
| 4 | +#include <atomic> |
4 | 5 | #include <cctype> |
5 | 6 | #include <utility> |
6 | 7 |
|
| 8 | +#ifndef _WIN32 |
| 9 | +#include <csignal> |
| 10 | +#endif |
| 11 | + |
7 | 12 | #include "cached_import/py_cached_import.h" |
8 | 13 | #include "common/constants.h" |
9 | 14 | #include "common/exception/not_implemented.h" |
@@ -183,34 +188,125 @@ void PyConnection::interrupt() { |
183 | 188 | static std::unordered_map<std::string, std::unique_ptr<Value>> transformPythonParameters( |
184 | 189 | const py::dict& params, Connection* conn); |
185 | 190 |
|
| 191 | +namespace { |
| 192 | + |
| 193 | +// Python runs its SIGINT handler only between bytecodes, so not while the main thread waits in |
| 194 | +// the engine. During such a wait, SIGINT interrupts the connection's query and then calls |
| 195 | +// Python's handler. This is only done on the main thread while Python's default handler |
| 196 | +// (KeyboardInterrupt) is active, so custom handlers keep their behaviour. |
| 197 | +class SigintInterruptsQuery { |
| 198 | +public: |
| 199 | + explicit SigintInterruptsQuery([[maybe_unused]] main::ClientContext* context) { |
| 200 | +#ifndef _WIN32 |
| 201 | + if (target.load() != nullptr || !onMainThreadWithDefaultHandler()) { |
| 202 | + return; |
| 203 | + } |
| 204 | + if (sigaction(SIGINT, nullptr, &previous) != 0) { |
| 205 | + return; |
| 206 | + } |
| 207 | + struct sigaction action {}; |
| 208 | + action.sa_sigaction = handle; |
| 209 | + action.sa_flags = SA_SIGINFO | (previous.sa_flags & SA_ONSTACK); |
| 210 | + sigemptyset(&action.sa_mask); |
| 211 | + received = false; |
| 212 | + target = context; |
| 213 | + installed = sigaction(SIGINT, &action, nullptr) == 0; |
| 214 | + if (!installed) { |
| 215 | + target = nullptr; |
| 216 | + } |
| 217 | +#endif |
| 218 | + } |
| 219 | + |
| 220 | + ~SigintInterruptsQuery() { |
| 221 | +#ifndef _WIN32 |
| 222 | + if (installed) { |
| 223 | + sigaction(SIGINT, &previous, nullptr); |
| 224 | + target = nullptr; |
| 225 | + } |
| 226 | +#endif |
| 227 | + } |
| 228 | + |
| 229 | + // Call with the GIL held once the query has returned. |
| 230 | + void raiseIfInterrupted() const { |
| 231 | +#ifndef _WIN32 |
| 232 | + if (installed && received && PyErr_CheckSignals() != 0) { |
| 233 | + throw py::error_already_set(); |
| 234 | + } |
| 235 | +#endif |
| 236 | + } |
| 237 | + |
| 238 | +private: |
| 239 | +#ifndef _WIN32 |
| 240 | + static bool onMainThreadWithDefaultHandler() { |
| 241 | + // The check must never fail a query, e.g. once imports stop working at shutdown. |
| 242 | + try { |
| 243 | + auto mainThread = importCache->threading.main_thread()(); |
| 244 | + if (mainThread.attr("ident").cast<unsigned long>() != PyThread_get_thread_ident()) { |
| 245 | + return false; |
| 246 | + } |
| 247 | + auto& signal = importCache->signal; |
| 248 | + return signal.getsignal()(signal.sigint()).is(signal.default_int_handler()); |
| 249 | + } catch (py::error_already_set&) { |
| 250 | + return false; |
| 251 | + } |
| 252 | + } |
| 253 | + |
| 254 | + static void handle(int signum, siginfo_t* info, void* ucontext) { |
| 255 | + if (auto* context = target.load()) { |
| 256 | + context->interrupt(); |
| 257 | + } |
| 258 | + received = true; |
| 259 | + // Python's handler only records the signal; KeyboardInterrupt is raised later. |
| 260 | + if (previous.sa_flags & SA_SIGINFO) { |
| 261 | + previous.sa_sigaction(signum, info, ucontext); |
| 262 | + } else if (previous.sa_handler != SIG_DFL && previous.sa_handler != SIG_IGN) { |
| 263 | + previous.sa_handler(signum); |
| 264 | + } |
| 265 | + } |
| 266 | + |
| 267 | + static inline std::atomic<main::ClientContext*> target{nullptr}; |
| 268 | + static inline std::atomic<bool> received{false}; |
| 269 | + static inline struct sigaction previous {}; |
| 270 | + bool installed = false; |
| 271 | +#endif |
| 272 | +}; |
| 273 | + |
| 274 | +} // namespace |
| 275 | + |
186 | 276 | std::unique_ptr<PyQueryResult> PyConnection::execute(PyPreparedStatement* preparedStatement, |
187 | 277 | const py::dict& params) { |
188 | 278 | auto& stateRef = refState(); |
189 | 279 | if (preparedStatement == nullptr || preparedStatement->state == nullptr) { |
190 | 280 | throw RuntimeException("Prepared statement is closed."); |
191 | 281 | } |
192 | 282 | auto parameters = transformPythonParameters(params, &stateRef.ref()); |
| 283 | + SigintInterruptsQuery sigint(stateRef.ref().getClientContext()); |
193 | 284 | py::gil_scoped_release release; |
194 | 285 | auto queryResult = |
195 | 286 | stateRef.ref().executeWithParams(&preparedStatement->state->ref(), std::move(parameters)); |
196 | 287 | py::gil_scoped_acquire acquire; |
| 288 | + sigint.raiseIfInterrupted(); |
197 | 289 | return checkAndWrapQueryResult(queryResult, state); |
198 | 290 | } |
199 | 291 |
|
200 | 292 | std::unique_ptr<PyQueryResult> PyConnection::query(const std::string& statement) { |
201 | 293 | auto& stateRef = refState(); |
| 294 | + SigintInterruptsQuery sigint(stateRef.ref().getClientContext()); |
202 | 295 | py::gil_scoped_release release; |
203 | 296 | auto queryResult = stateRef.ref().query(statement); |
204 | 297 | py::gil_scoped_acquire acquire; |
| 298 | + sigint.raiseIfInterrupted(); |
205 | 299 | return checkAndWrapQueryResult(queryResult, state); |
206 | 300 | } |
207 | 301 |
|
208 | 302 | std::unique_ptr<PyQueryResult> PyConnection::queryAsArrow(const std::string& statement, |
209 | 303 | int64_t chunkSize) { |
210 | 304 | auto& stateRef = refState(); |
| 305 | + SigintInterruptsQuery sigint(stateRef.ref().getClientContext()); |
211 | 306 | py::gil_scoped_release release; |
212 | 307 | auto queryResult = stateRef.ref().queryAsArrow(statement, chunkSize); |
213 | 308 | py::gil_scoped_acquire acquire; |
| 309 | + sigint.raiseIfInterrupted(); |
214 | 310 | return checkAndWrapQueryResult(queryResult, state); |
215 | 311 | } |
216 | 312 |
|
|
0 commit comments