/home/uke/oil/mycpp/gc_iolib.h
Line | Count | Source (jump to first uncovered line) |
1 | | // gc_iolib.h - corresponds to mycpp/iolib.py |
2 | | |
3 | | #ifndef MYCPP_GC_IOLIB_H |
4 | | #define MYCPP_GC_IOLIB_H |
5 | | |
6 | | // For now, we assume that simple int and pointer operations are atomic, rather |
7 | | // than using std::atomic. Could be a ./configure option later. |
8 | | // |
9 | | // See doc/portability.md. |
10 | | |
11 | | #define LOCK_FREE_ATOMICS 0 |
12 | | |
13 | | #if LOCK_FREE_ATOMICS |
14 | | #include <atomic> |
15 | | #endif |
16 | | #include <signal.h> |
17 | | |
18 | | #include "mycpp/gc_list.h" |
19 | | |
20 | | namespace iolib { |
21 | | |
22 | | const int UNTRAPPED_SIGWINCH = -10; |
23 | | |
24 | | // Make the signal queue slab 4096 bytes, including the GC header. See |
25 | | // cpp/core_test.cc. |
26 | | const int kMaxPendingSignals = 1022; |
27 | | |
28 | | class SignalSafe { |
29 | | // State that is shared between the main thread and signal handlers. |
30 | | public: |
31 | | SignalSafe() |
32 | | : pending_signals_(AllocSignalList()), |
33 | | empty_list_(AllocSignalList()), // to avoid repeated allocation |
34 | | last_sig_num_(0), |
35 | | received_sigint_(false), |
36 | | received_sigwinch_(false), |
37 | | sigwinch_code_(UNTRAPPED_SIGWINCH), |
38 | 4 | num_dropped_(0) { |
39 | 4 | } |
40 | | |
41 | | // Called from signal handling context. Do not allocate. |
42 | 2.07k | void UpdateFromSignalHandler(int sig_num) { |
43 | 2.07k | if (pending_signals_->len_ < pending_signals_->capacity_) { |
44 | | // We can append without allocating |
45 | 2.05k | pending_signals_->append(sig_num); |
46 | 2.05k | } else { |
47 | | // Unlikely: we would have to allocate. Just increment a counter, which |
48 | | // we could expose somewhere in the UI. |
49 | 20 | num_dropped_++; |
50 | 20 | } |
51 | | |
52 | 2.07k | if (sig_num == SIGINT) { |
53 | 2.06k | received_sigint_ = true; |
54 | 2.06k | } |
55 | | |
56 | 2.07k | if (sig_num == SIGWINCH) { |
57 | 4 | received_sigwinch_ = true; |
58 | 4 | sig_num = sigwinch_code_; // mutate param |
59 | 4 | } |
60 | | |
61 | | #if LOCK_FREE_ATOMICS |
62 | | last_sig_num_.store(sig_num); |
63 | | #else |
64 | 2.07k | last_sig_num_ = sig_num; |
65 | 2.07k | #endif |
66 | 2.07k | } |
67 | | |
68 | | // Main thread takes signals so it can run traps. |
69 | 10 | List<int>* TakePendingSignals() { |
70 | 10 | List<int>* ret = pending_signals_; |
71 | | |
72 | | // Make sure we have a distinct list to reuse. |
73 | 10 | DCHECK(empty_list_ != pending_signals_); |
74 | 0 | pending_signals_ = empty_list_; |
75 | | |
76 | 10 | return ret; |
77 | 10 | } |
78 | | |
79 | | // Main thread returns the same list as an optimization to avoid allocation. |
80 | 6 | void ReuseEmptyList(List<int>* empty_list) { |
81 | 6 | DCHECK(empty_list != pending_signals_); // must be different |
82 | 6 | DCHECK(len(empty_list) == 0); // main thread clears |
83 | 6 | DCHECK(empty_list->capacity_ == kMaxPendingSignals); |
84 | | |
85 | 0 | empty_list_ = empty_list; |
86 | 6 | } |
87 | | |
88 | | // Main thread wants to get the last signal received. |
89 | 8 | int LastSignal() { |
90 | | #if LOCK_FREE_ATOMICS |
91 | | return last_sig_num_.load(); |
92 | | #else |
93 | 8 | return last_sig_num_; |
94 | 8 | #endif |
95 | 8 | } |
96 | | |
97 | 0 | void SetSigIntTrapped(bool b) { |
98 | 0 | sigint_trapped_ = b; |
99 | 0 | } |
100 | | |
101 | | // Used by pyos.WaitPid, Read, ReadByte. |
102 | 0 | bool PollSigInt() { |
103 | 0 | bool result = received_sigint_; |
104 | 0 | received_sigint_ = false; |
105 | 0 | return result; |
106 | 0 | } |
107 | | |
108 | | // Used by osh/cmd_eval.py. Main loop wants to know if SIGINT was received |
109 | | // since the last time PollSigInt was called. |
110 | 0 | bool PollUntrappedSigInt() { |
111 | 0 | bool received = PollSigInt(); // clears a flag |
112 | 0 | return received && !sigint_trapped_; |
113 | 0 | } |
114 | | |
115 | | // Main thread tells us whether SIGWINCH is trapped. |
116 | 2 | void SetSigWinchCode(int code) { |
117 | 2 | sigwinch_code_ = code; |
118 | 2 | } |
119 | | |
120 | | // Main thread wants to know if SIGWINCH was received since the last time |
121 | | // PollSigWinch was called. |
122 | 0 | bool PollSigWinch() { |
123 | 0 | bool result = received_sigwinch_; |
124 | 0 | received_sigwinch_ = false; |
125 | 0 | return result; |
126 | 0 | } |
127 | | |
128 | 2 | static constexpr uint32_t field_mask() { |
129 | 2 | return maskbit(offsetof(SignalSafe, pending_signals_)) | |
130 | 2 | maskbit(offsetof(SignalSafe, empty_list_)); |
131 | 2 | } |
132 | | |
133 | 2 | static constexpr ObjHeader obj_header() { |
134 | 2 | return ObjHeader::ClassFixed(field_mask(), sizeof(SignalSafe)); |
135 | 2 | } |
136 | | |
137 | | List<int>* pending_signals_; // public for testing |
138 | | List<int>* empty_list_; |
139 | | |
140 | | private: |
141 | | // Enforce private state because two different "threads" will use it! |
142 | | |
143 | | // Reserve a fixed number of signals. |
144 | 8 | List<int>* AllocSignalList() { |
145 | 8 | List<int>* ret = NewList<int>(); |
146 | 8 | ret->reserve(kMaxPendingSignals); |
147 | 8 | return ret; |
148 | 8 | } |
149 | | |
150 | | #if LOCK_FREE_ATOMICS |
151 | | std::atomic<int> last_sig_num_; |
152 | | #else |
153 | | int last_sig_num_; |
154 | | #endif |
155 | | // Not sufficient: volatile sig_atomic_t last_sig_num_; |
156 | | |
157 | | bool sigint_trapped_; |
158 | | int received_sigint_; |
159 | | int received_sigwinch_; |
160 | | int sigwinch_code_; |
161 | | int num_dropped_; |
162 | | }; |
163 | | |
164 | | extern SignalSafe* gSignalSafe; |
165 | | |
166 | | // Allocate global and return it. |
167 | | SignalSafe* InitSignalSafe(); |
168 | | |
169 | | void RegisterSignalInterest(int sig_num); |
170 | | |
171 | | void sigaction(int sig_num, void (*handler)(int)); |
172 | | |
173 | | } // namespace iolib |
174 | | |
175 | | #endif // MYCPP_GC_IOLIB_H |