forked from rstudio/reticulate
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathevent_loop.cpp
156 lines (127 loc) · 5.82 KB
/
event_loop.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// This code implements the ability to poll for events while Python code
// is executing. Events of interest include checking for interrupts as well
// as executing GUI front end code.
//
// Since execution of Python code occurs within native C++ code the normal R
// interpeter event polling that occurs during do_eval does not have
// a chance to execute. Typically within Rcpp packages long running user code
// will call Rcpp::checkForInterrupts periodically to check for an interrupt
// and exit via an exception if there is one (tthis call to checkForInterrupts
// calls the R process events machinery as well). However there is no opportunity
// to do this during Python execution, so we need to find a way to get periodic
// callbacks during the Python interpeter's processing to perform this check.
//
// This is provided for via the Py_AddPendingCall function, which enables the
// scheduling of a C callback on the main interpeter thread *during* the
// execution of Python code. Unfortunately this call occurs very eagerly, so we
// can't just schedule a callback and have the callback reschedule itself (this
// completely swamps the interpeteter). Rather, we need to have a background
// thread that will perform the Py_AddPendingCall on a throttled basis (both
// time wise and in terms of only scheduling an additional callback while the
// Python interpeter remains running).
//
// Having arranged for callbacks during Python interpretation at the
// appropriate rate, we then need to interact with R to check for interrupts
// (and thus pump events), then interact with Python to notify it of any
// interrupt. We poll for interrupts in R using the same technique as
// Rcpp::checkForInterrupts. If we determine that an interrupt has occurred
// we call PyErr_SetInterrupt, which signals Python that an interrupt has been
// requested, which will ultimately result in a KeyboardInterrupt error being
// raised.
//
#include "event_loop.h"
#include "libpython.h"
using namespace libpython;
#include "tinythread.h"
using namespace tthread;
#include <Rinternals.h>
#include <vector>
namespace event_loop {
namespace {
// Class that is used to signal the need to poll for interrupts between
// threads. The function called by the Python interpreter during execution
// (pollForEvents) always calls requestPolling to keep polling alive. The
// background thread periodically attmeps to "collect" this request and if
// successful re-schedules the pollForEvents function using
// Py_AddPendingCall. This allows us to prevent the background thread from
// continually scheduling pollForEvents even when the Python interpreter is
// not running (because once pollForEvents is no longer being called by the
// Python interpreter no additonal calls to pollForEvents will be
// scheduled)
class EventPollingSignal {
public:
EventPollingSignal() : pollingRequested_(true) {}
void requestPolling() {
lock_guard<mutex> lock(mutex_);
pollingRequested_ = true;
}
bool collectRequest() {
lock_guard<mutex> lock(mutex_);
bool requested = pollingRequested_;
pollingRequested_ = false;
return requested;
}
private:
EventPollingSignal(const EventPollingSignal& other);
EventPollingSignal& operator=(const EventPollingSignal&);
private:
mutex mutex_;
bool pollingRequested_;
};
EventPollingSignal s_pollingSignal;
extern "C" {
// Forward declarations
int pollForEvents(void*);
void checkUserInterrupt(void*);
// Background thread which re-schedules pollForEvents on the main Python
// interpreter thread every 250ms so long as the Python interpeter is still
// running (when it stops running it will stop calling pollForEvents and
// the polling signal will not be set).
void eventPollingWorker(void *) {
while(true) {
// Throttle via sleep
this_thread::sleep_for(chrono::milliseconds(250));
// Schedule polling on the main thread if the interpeter is still running
// Note that Py_AddPendingCall is documented to be callable from a background
// thread: "This function doesn’t need a current thread state to run, and it
// doesn’t need the global interpreter lock."
// (see: https://docs.python.org/3/c-api/init.html#c.Py_AddPendingCall)
if (s_pollingSignal.collectRequest())
Py_AddPendingCall(pollForEvents, NULL);
}
}
// Callback function scheduled to run on the main Python interpreter loop. This
// is scheduled using Py_AddPendingCall, which ensures that it is run on the
// main thread while the interpreter is executing. Note that we can't just have
// this function keep reschedulding itself or the interpreter would be swamped
// with just calling and re-calling this function. Rather, we need to throttle
// the scheduling of the function by using a background thread + a sleep timer.
int pollForEvents(void*) {
// Check whether an interrupt has been requested by the user. If one
// has then set the Python interrupt flag (which will soon after result
// in a KeyboardInterrupt error being thrown).
if (R_ToplevelExec(checkUserInterrupt, NULL) == FALSE)
PyErr_SetInterrupt();
// Request that the background thread schedule us to be called again
// (this is delegated to a background thread so that these requests
// can be throttled)
s_pollingSignal.requestPolling();
// Success
return 0;
}
// Wrapper for calling R_CheckUserInterrupt within R_TolevelExec. Note that
// this call will result in R calling it's internal R_ProcessEvents function
// which will allow front-ends to pump events, set the interrupt pending flag,
// etc. This function may also jump_to_top, but these jumps are caught by
// R_ToplevelExec and results in a return value of FALSE.
void checkUserInterrupt(void*) {
R_CheckUserInterrupt();
}
} // extern "C"
} // anonymous namespace
// Initialize event loop polling background thread
void initialize() {
thread t(eventPollingWorker, NULL);
t.detach();
}
} // namespace event_loop