cpp

Coverage Report

Created: 2025-05-25 11:52

/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
0
        num_dropped_(0) {
39
0
  }
40
41
  // Called from signal handling context.  Do not allocate.
42
0
  void UpdateFromSignalHandler(int sig_num) {
43
0
    if (pending_signals_->len_ < pending_signals_->capacity_) {
44
      // We can append without allocating
45
0
      pending_signals_->append(sig_num);
46
0
    } else {
47
      // Unlikely: we would have to allocate.  Just increment a counter, which
48
      // we could expose somewhere in the UI.
49
0
      num_dropped_++;
50
0
    }
51
52
0
    if (sig_num == SIGINT) {
53
0
      received_sigint_ = true;
54
0
    }
55
56
0
    if (sig_num == SIGWINCH) {
57
0
      received_sigwinch_ = true;
58
0
      sig_num = sigwinch_code_;  // mutate param
59
0
    }
60
61
#if LOCK_FREE_ATOMICS
62
    last_sig_num_.store(sig_num);
63
#else
64
0
    last_sig_num_ = sig_num;
65
0
#endif
66
0
  }
67
68
  // Main thread takes signals so it can run traps.
69
0
  List<int>* TakePendingSignals() {
70
0
    List<int>* ret = pending_signals_;
71
0
72
0
    // Make sure we have a distinct list to reuse.
73
0
    DCHECK(empty_list_ != pending_signals_);
74
0
    pending_signals_ = empty_list_;
75
0
76
0
    return ret;
77
0
  }
78
79
  // Main thread returns the same list as an optimization to avoid allocation.
80
0
  void ReuseEmptyList(List<int>* empty_list) {
81
0
    DCHECK(empty_list != pending_signals_);  // must be different
82
0
    DCHECK(len(empty_list) == 0);            // main thread clears
83
0
    DCHECK(empty_list->capacity_ == kMaxPendingSignals);
84
0
85
0
    empty_list_ = empty_list;
86
0
  }
87
88
  // Main thread wants to get the last signal received.
89
0
  int LastSignal() {
90
0
#if LOCK_FREE_ATOMICS
91
0
    return last_sig_num_.load();
92
0
#else
93
0
    return last_sig_num_;
94
0
#endif
95
0
  }
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
0
  void SetSigWinchCode(int code) {
117
0
    sigwinch_code_ = code;
118
0
  }
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
0
  static constexpr uint32_t field_mask() {
129
0
    return maskbit(offsetof(SignalSafe, pending_signals_)) |
130
0
           maskbit(offsetof(SignalSafe, empty_list_));
131
0
  }
132
133
0
  static constexpr ObjHeader obj_header() {
134
0
    return ObjHeader::ClassFixed(field_mask(), sizeof(SignalSafe));
135
0
  }
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
0
  List<int>* AllocSignalList() {
145
0
    List<int>* ret = NewList<int>();
146
0
    ret->reserve(kMaxPendingSignals);
147
0
    return ret;
148
0
  }
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