vernier 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faf587f758e0b9a68adc377bf27c0f35eb0605703b088e3e122f969f513c864d
4
- data.tar.gz: 6cbefbfa78fb3ef1e92c7cfdd5ba0dd43f59b1f63cce6583fe18f4e667fd61db
3
+ metadata.gz: b3847eff8849b364e1cd777058a720d4960e7058c4895c86ded9c5f98d053e76
4
+ data.tar.gz: 306275958737222981ec6fd45846b210b757bc858feee84533dbc45b3997d471
5
5
  SHA512:
6
- metadata.gz: 402ce92fbe0f408906fe368e0e2edc637dd68f08476a690877166cb1628068fd6bbfd11878d625276e8f3f785c20adc49ef19494d9e81162d201930b4c64c81f
7
- data.tar.gz: 8b7d6c687873ed991b23a508231471b445d6960b47bac2045f1f9eee9f5d82584490a0278f2344d0001e3f5367fffcdfc29fbc284bed448c4155e4381aa26292
6
+ metadata.gz: 803e19370d96f59ddf2668fadd2c4f0318c98d60576ad9f34a7a229495c6c9185d5fa5bc461b998722ca83979fb05a9007120efeaa206a734eff05bb0e3b864e
7
+ data.tar.gz: 3bab21c2bcb0cdaf82287cd7fbe9a619bba3bdf0658d6ec3cdd7d9b7cdae6feec5f43f1cc2056dd86af31fdf19794173e12d651fd26620f5e05304f7aa1ecf5b
data/README.md CHANGED
@@ -22,7 +22,7 @@ Rails benchmark - lobste.rs (time)
22
22
 
23
23
  ## Installation
24
24
 
25
- Vernier requires Ruby version 3.2.1 or greater.
25
+ Vernier requires Ruby version 3.2.1 or greater and a Unix-like operating system.
26
26
 
27
27
  ```ruby
28
28
  gem "vernier", "~> 1.0"
@@ -30,7 +30,7 @@ gem "vernier", "~> 1.0"
30
30
 
31
31
  ## Usage
32
32
 
33
- The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the firefox profiler frontend, which profiles are also compatible with) or by using the `vernier view` command in the CLI.
33
+ The output can be viewed in the web app at https://vernier.prof, locally using the [`profile-viewer` gem](https://github.com/tenderlove/profiler/tree/ruby) (both lightly customized versions of the firefox profiler frontend which profiles are compatible with), or by using the `vernier view` command in the CLI.
34
34
 
35
35
  - **Flame Graph**: Shows proportionally how much time is spent within particular stack frames. Frames are grouped together, which means that x-axis / left-to-right order is not meaningful.
36
36
  - **Stack Chart**: Shows the stack at each sample with the x-axis representing time and can be read left-to-right.
@@ -41,14 +41,14 @@ The output can be viewed in the web app at https://vernier.prof, locally using t
41
41
 
42
42
  The easiest way to record a program or script is via the CLI:
43
43
 
44
- ```
44
+ ```sh
45
45
  $ vernier run -- ruby -e 'sleep 1'
46
46
  starting profiler with interval 500 and allocation interval 0
47
47
  #<Vernier::Result 1.001589 seconds, 1 threads, 1 samples, 1 unique>
48
48
  written to /tmp/profile20240328-82441-gkzffc.vernier.json.gz
49
49
  ```
50
50
 
51
- ```
51
+ ```sh
52
52
  $ vernier run --interval 100 --allocation-interval 10 -- ruby -e '10.times { Object.new }'
53
53
  starting profiler with interval 100 and allocation interval 10
54
54
  #<Vernier::Result 0.00067 seconds, 1 threads, 1 samples, 1 unique>
@@ -85,13 +85,39 @@ some_other_slow_method
85
85
  Vernier.stop_profile
86
86
  ```
87
87
 
88
+ #### Rack middleware
89
+
90
+ You can also use `Vernier::Middleware` to profile a Rack application:
91
+
92
+ ```ruby
93
+ # config.ru
94
+
95
+ require "vernier"
96
+
97
+ use Vernier::Middleware
98
+
99
+ run ->(env) { [200, { "Content-Type" => "text/plain" }, ["Hello, Profiling World!"]] }
100
+ ```
101
+
102
+ If you're using Rails, you can add the middleware to your `config/application.rb`:
103
+
104
+ ```ruby
105
+ config.middleware.use Vernier::Middleware, permit: ->(env) { env["PATH_INFO"].start_with?("/api") }
106
+ ```
107
+
108
+ You can then enable profiling and configure options with query parameters:
109
+
110
+ ```sh
111
+ curl http://localhost:3000?vernier=true&vernier_interval=100&vernier_allocation_interval=10
112
+ ```
113
+
88
114
  ### Retained memory
89
115
 
90
116
  #### Block of code
91
117
 
92
118
  Record a flamegraph of all **retained** allocations from loading `irb`:
93
119
 
94
- ```
120
+ ```sh
95
121
  ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "irb" }'
96
122
  ```
97
123
 
@@ -100,13 +126,13 @@ ruby -r vernier -e 'Vernier.trace_retained(out: "irb_profile.json") { require "i
100
126
 
101
127
  ### Options
102
128
 
103
- Option | Description
104
- :- | :-
105
- `mode` | The sampling mode to use. One of `:wall`, `:retained` or `:custom`. Default is `:wall`.
106
- `out` | The file to write the profile to.
107
- `interval` | The sampling interval in microseconds. Default is `500`. Only available in `:wall` mode.
108
- `allocation_interval` | The allocation sampling interval in number of allocations. Default is `0` (disabled). Only available in `:wall` mode.
109
- `gc` | Initiate a full and immediate garbage collection cycle before profiling. Default is `true`. Only available in `:retained` mode.
129
+ | Option | Middleware Param | Description | Default (Middleware Default) |
130
+ |-----------------------|-------------------------------|---------------------------------------------------------------|------------------------------|
131
+ | `mode` | N/A | Sampling mode: `:wall`, `:retained`, or `:custom`. | `:wall` (`:wall`) |
132
+ | `out` | N/A | File to write the profile to. | N/A (Auto-generated) |
133
+ | `interval` | `vernier_interval` | Sampling interval (µs). Only in `:wall` mode. | `500` (`200`) |
134
+ | `allocation_interval` | `vernier_allocation_interval` | Allocation sampling interval. Only in `:wall` mode. | `0` (disabled) (`200`) |
135
+ | `gc` | N/A | Run full GC cycle before profiling. Only in `:retained` mode. | `true` (N/A) |
110
136
 
111
137
  ## Development
112
138
 
@@ -0,0 +1,51 @@
1
+ require "bundler/inline"
2
+ gemfile do
3
+ source 'https://rubygems.org'
4
+
5
+ gem "async"
6
+ end
7
+
8
+ require "async"
9
+ require "async/queue"
10
+
11
+ def measure
12
+ x = Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ yield
14
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - x
15
+ end
16
+
17
+ def fib(n)
18
+ if n < 2
19
+ n
20
+ else
21
+ fib(n-2) + fib(n-1)
22
+ end
23
+ end
24
+
25
+ # find fib that takes ~50ms
26
+ fib_i = 50.times.find { |i| measure { fib(i) } >= 0.05 }
27
+ sleep_i = measure { fib(fib_i) }
28
+
29
+ Async {
30
+ latch = Async::Queue.new
31
+
32
+ workers = [
33
+ Async {
34
+ latch.pop # block until ready to measure
35
+
36
+ 100.times {
37
+ sleep(sleep_i)
38
+ # stalls happen here. This worker wants to be scheduled so it can
39
+ # continue the loop, but will be blocked by another worker executing fib
40
+ }
41
+ },
42
+ Async {
43
+ latch.pop # block until ready to measure
44
+
45
+ 100.times { fib(fib_i) }
46
+ },
47
+ ]
48
+
49
+ 2.times { latch << nil }
50
+ workers.each(&:wait)
51
+ }
data/exe/vernier CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
4
+
3
5
  require "optparse"
4
6
  require "vernier/version"
5
7
 
@@ -18,6 +20,9 @@ FLAGS:
18
20
  o.on('--output [FILENAME]', String, "output filename") do |s|
19
21
  options[:output] = s
20
22
  end
23
+ o.on('--output-dir [DIRECTORY]', String, "output directory (default .)") do |s|
24
+ options[:output_dir] = s
25
+ end
21
26
  o.on('--interval [MICROSECONDS]', Integer, "sampling interval (default 500)") do |i|
22
27
  options[:interval] = i
23
28
  end
@@ -52,60 +57,14 @@ FLAGS:
52
57
 
53
58
  def self.inverted_tree(top, file)
54
59
  # Print the inverted tree from a Vernier profile
55
- require "json"
56
-
57
- is_gzip = File.binread(file, 2) == "\x1F\x8B".b # check for gzip header
58
-
59
- json = if is_gzip
60
- require "zlib"
61
- Zlib::GzipReader.open(file) { |gz| gz.read }
62
- else
63
- File.read file
64
- end
65
-
66
- info = JSON.load json
67
-
68
- main = info["threads"].find { |thread| thread["isMainThread"] }
69
-
70
- weight_by_frame = Hash.new(0)
71
-
72
- stack_frames = main["stackTable"]["frame"]
73
- frame_table = main["frameTable"]["func"]
74
- func_table = main["funcTable"]["name"]
75
- string_array = main["stringArray"]
60
+ require "vernier/parsed_profile"
61
+ require "vernier/output/top"
62
+ require "vernier/output/file_listing"
76
63
 
77
- main["samples"]["stack"].zip(main["samples"]["weight"]).each do |stack, weight|
78
- top_frame_index = stack_frames[stack]
79
- func_index = frame_table[top_frame_index]
80
- string_index = func_table[func_index]
81
- str = string_array[string_index]
82
- weight_by_frame[str] += weight
83
- end
84
-
85
- total = weight_by_frame.values.inject :+
86
-
87
- header = ["Samples", "%", ""]
88
- widths = header.map(&:bytesize)
89
-
90
- columns = weight_by_frame.sort_by { |k,v| v }.reverse.first(top).map { |k,v|
91
- entry = [v.to_s, ((v / total.to_f) * 100).round(1).to_s, k]
92
- entry.each_with_index { |str, i| widths[i] = str.bytesize if widths[i] < str.bytesize }
93
- entry
94
- }
95
-
96
- print_separator widths
97
- print_row header, widths
98
- print_separator widths
99
- columns.each { print_row(_1, widths) }
100
- print_separator widths
101
- end
102
-
103
- def self.print_row(list, widths)
104
- puts("|" + list.map.with_index { |str, i| " " + str.ljust(widths[i] + 1) }.join("|") + "|")
105
- end
64
+ parsed_profile = Vernier::ParsedProfile.read_file(file)
106
65
 
107
- def self.print_separator(widths)
108
- puts("+" + widths.map { |i| "-" * (i + 2) }.join("+") + "+")
66
+ puts Vernier::Output::Top.new(parsed_profile).output
67
+ puts Vernier::Output::FileListing.new(parsed_profile).output
109
68
  end
110
69
  end
111
70
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mkmf"
4
4
 
5
+ $CXXFLAGS += " -fvisibility=hidden "
5
6
  $CXXFLAGS += " -std=c++14 "
6
7
  $CXXFLAGS += " -ggdb3 -Og "
7
8
 
@@ -11,5 +12,6 @@ have_struct_member("rb_internal_thread_event_data_t", "thread", ["ruby/thread.h"
11
12
  have_func("rb_profile_thread_frames", "ruby/debug.h")
12
13
 
13
14
  have_func("pthread_setname_np")
15
+ have_func("pthread_condattr_setclock")
14
16
 
15
17
  create_makefile("vernier/vernier")
@@ -0,0 +1,144 @@
1
+ #include <mutex>
2
+ #include <stdio.h>
3
+ #include <unistd.h>
4
+ #include <vector>
5
+
6
+ #include "vernier.hh"
7
+ #include "timestamp.hh"
8
+ #include "periodic_thread.hh"
9
+
10
+ #if defined(__APPLE__)
11
+
12
+ // Based loosely on https://github.com/zombocom/get_process_mem
13
+ #include <libproc.h>
14
+
15
+ uint64_t memory_rss() {
16
+ pid_t pid = getpid();
17
+
18
+ struct proc_taskinfo tinfo;
19
+ int st = proc_pidinfo(pid, PROC_PIDTASKINFO, 0,
20
+ &tinfo, sizeof(tinfo));
21
+
22
+ if (st != sizeof(tinfo)) {
23
+ fprintf(stderr, "VERNIER: warning: proc_pidinfo failed\n");
24
+ return 0;
25
+ }
26
+
27
+ return tinfo.pti_resident_size;
28
+ }
29
+
30
+ #elif defined(__linux__)
31
+
32
+ uint64_t memory_rss() {
33
+ long rss = 0;
34
+
35
+ // I'd heard that you shouldn't read /proc/*/smaps with fopen and family,
36
+ // but maybe it's fine for statm which is much smaller and will almost
37
+ // certainly fit in any internal buffer.
38
+ FILE *file = fopen("/proc/self/statm", "r");
39
+ if (!file) return 0;
40
+ if (fscanf(file, "%*s%ld", &rss) != 1) {
41
+ fclose(file);
42
+ return 0;
43
+ }
44
+ fclose(file);
45
+ return rss * sysconf(_SC_PAGESIZE);
46
+ }
47
+
48
+ #else
49
+
50
+ // Unsupported
51
+ uint64_t memory_rss() {
52
+ return 0;
53
+ }
54
+
55
+ #endif
56
+
57
+ VALUE rb_cMemoryTracker;
58
+
59
+ static VALUE rb_memory_rss(VALUE self) {
60
+ return ULL2NUM(memory_rss());
61
+ }
62
+
63
+ class MemoryTracker : public PeriodicThread {
64
+ public:
65
+ struct Record {
66
+ TimeStamp timestamp;
67
+ uint64_t memory_rss;
68
+ };
69
+ std::vector<Record> results;
70
+ std::mutex mutex;
71
+
72
+ MemoryTracker() : PeriodicThread(TimeStamp::from_milliseconds(10)) {
73
+ }
74
+
75
+ void run_iteration() {
76
+ record();
77
+ }
78
+
79
+ void record() {
80
+ const std::lock_guard<std::mutex> lock(mutex);
81
+ results.push_back(Record{TimeStamp::Now(), memory_rss()});
82
+ }
83
+ };
84
+
85
+ static const rb_data_type_t rb_memory_tracker_type = {
86
+ .wrap_struct_name = "vernier/memory_tracker",
87
+ .function = {
88
+ //.dmemsize = memory_tracker_memsize,
89
+ //.dmark = memory_tracker_mark,
90
+ //.dfree = memory_tracker_free,
91
+ },
92
+ };
93
+
94
+ VALUE memory_tracker_start(VALUE self) {
95
+ MemoryTracker *memory_tracker;
96
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
97
+ memory_tracker->start();
98
+ return self;
99
+ }
100
+
101
+ VALUE memory_tracker_stop(VALUE self) {
102
+ MemoryTracker *memory_tracker;
103
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
104
+
105
+ memory_tracker->stop();
106
+ return self;
107
+ }
108
+
109
+ VALUE memory_tracker_record(VALUE self) {
110
+ MemoryTracker *memory_tracker;
111
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
112
+ memory_tracker->record();
113
+ return self;
114
+ }
115
+
116
+ VALUE memory_tracker_results(VALUE self) {
117
+ MemoryTracker *memory_tracker;
118
+ TypedData_Get_Struct(self, MemoryTracker, &rb_memory_tracker_type, memory_tracker);
119
+ VALUE timestamps = rb_ary_new();
120
+ VALUE memory = rb_ary_new();
121
+ for (const auto& record: memory_tracker->results) {
122
+ rb_ary_push(timestamps, ULL2NUM(record.timestamp.nanoseconds()));
123
+ rb_ary_push(memory, ULL2NUM(record.memory_rss));
124
+ }
125
+ return rb_ary_new_from_args(2, timestamps, memory);
126
+ }
127
+
128
+ VALUE memory_tracker_alloc(VALUE self) {
129
+ auto memory_tracker = new MemoryTracker();
130
+ VALUE obj = TypedData_Wrap_Struct(self, &rb_memory_tracker_type, memory_tracker);
131
+ return obj;
132
+ }
133
+
134
+ void Init_memory() {
135
+ rb_cMemoryTracker = rb_define_class_under(rb_mVernier, "MemoryTracker", rb_cObject);
136
+ rb_define_alloc_func(rb_cMemoryTracker, memory_tracker_alloc);
137
+
138
+ rb_define_method(rb_cMemoryTracker, "start", memory_tracker_start, 0);
139
+ rb_define_method(rb_cMemoryTracker, "stop", memory_tracker_stop, 0);
140
+ rb_define_method(rb_cMemoryTracker, "results", memory_tracker_results, 0);
141
+ rb_define_method(rb_cMemoryTracker, "record", memory_tracker_record, 0);
142
+
143
+ rb_define_singleton_method(rb_mVernier, "memory_rss", rb_memory_rss, 0);
144
+ }
@@ -0,0 +1,141 @@
1
+ #include "ruby.h"
2
+
3
+ #include <atomic>
4
+ #include "timestamp.hh"
5
+
6
+ #ifdef __APPLE__
7
+
8
+ #include <mach/mach.h>
9
+ #include <mach/mach_time.h>
10
+ #include <pthread.h>
11
+
12
+ // https://developer.apple.com/library/archive/technotes/tn2169/_index.html
13
+ inline void upgrade_thread_priority(pthread_t pthread) {
14
+ mach_timebase_info_data_t timebase_info;
15
+ mach_timebase_info(&timebase_info);
16
+
17
+ const uint64_t NANOS_PER_MSEC = 1000000ULL;
18
+ double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
19
+
20
+ thread_time_constraint_policy_data_t policy;
21
+ policy.period = 0;
22
+
23
+ // FIXME: I really don't know what these value should be
24
+ policy.computation = (uint32_t)(5 * clock2abs); // 5 ms of work
25
+ policy.constraint = (uint32_t)(10 * clock2abs);
26
+ policy.preemptible = FALSE;
27
+
28
+ int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
29
+ THREAD_TIME_CONSTRAINT_POLICY,
30
+ (thread_policy_t)&policy,
31
+ THREAD_TIME_CONSTRAINT_POLICY_COUNT);
32
+
33
+ if (kr != KERN_SUCCESS) {
34
+ mach_error("thread_policy_set:", kr);
35
+ exit(1);
36
+ }
37
+ }
38
+ #else
39
+ inline void upgrade_thread_priority(pthread_t pthread) {
40
+ }
41
+ #endif
42
+
43
+ class PeriodicThread {
44
+ pthread_t pthread;
45
+ TimeStamp interval;
46
+
47
+ pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER;
48
+ pthread_cond_t running_cv;
49
+ std::atomic_bool running;
50
+
51
+ public:
52
+ PeriodicThread(TimeStamp interval) : interval(interval), running(false) {
53
+ pthread_condattr_t attr;
54
+ pthread_condattr_init(&attr);
55
+ #if HAVE_PTHREAD_CONDATTR_SETCLOCK
56
+ pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
57
+ #endif
58
+ pthread_cond_init(&running_cv, &attr);
59
+ }
60
+
61
+ void set_interval(TimeStamp timestamp) {
62
+ interval = timestamp;
63
+ }
64
+
65
+ static void *thread_entrypoint(void *arg) {
66
+ upgrade_thread_priority(pthread_self());
67
+
68
+ static_cast<PeriodicThread *>(arg)->run();
69
+ return NULL;
70
+ }
71
+
72
+ void run() {
73
+ #if HAVE_PTHREAD_SETNAME_NP
74
+ #ifdef __APPLE__
75
+ pthread_setname_np("Vernier profiler");
76
+ #else
77
+ pthread_setname_np(pthread_self(), "Vernier profiler");
78
+ #endif
79
+ #endif
80
+
81
+ TimeStamp next_sample_schedule = TimeStamp::Now();
82
+ bool done = false;
83
+ while (!done) {
84
+ TimeStamp sample_complete = TimeStamp::Now();
85
+
86
+ run_iteration();
87
+
88
+ next_sample_schedule += interval;
89
+
90
+ if (next_sample_schedule < sample_complete) {
91
+ next_sample_schedule = sample_complete + interval;
92
+ }
93
+
94
+ pthread_mutex_lock(&running_mutex);
95
+ if (running) {
96
+ #if HAVE_PTHREAD_CONDATTR_SETCLOCK
97
+ struct timespec next_sample_ts = next_sample_schedule.timespec();
98
+ #else
99
+ auto offset = TimeStamp::NowRealtime() - TimeStamp::Now();
100
+ struct timespec next_sample_ts = (next_sample_schedule + offset).timespec();
101
+ #endif
102
+ int ret;
103
+ do {
104
+ ret = pthread_cond_timedwait(&running_cv, &running_mutex, &next_sample_ts);
105
+ } while(running && ret == EINTR);
106
+ }
107
+ done = !running;
108
+ pthread_mutex_unlock(&running_mutex);
109
+ }
110
+ }
111
+
112
+ virtual void run_iteration() = 0;
113
+
114
+ void start() {
115
+ pthread_mutex_lock(&running_mutex);
116
+ if (!running) {
117
+ running = true;
118
+
119
+ int ret = pthread_create(&pthread, NULL, &thread_entrypoint, this);
120
+ if (ret != 0) {
121
+ perror("pthread_create");
122
+ rb_bug("VERNIER: pthread_create failed");
123
+ }
124
+ }
125
+ pthread_mutex_unlock(&running_mutex);
126
+ }
127
+
128
+ void stop() {
129
+ pthread_mutex_lock(&running_mutex);
130
+ bool was_running = running;
131
+ if (running) {
132
+ running = false;
133
+ pthread_cond_broadcast(&running_cv);
134
+ }
135
+ pthread_mutex_unlock(&running_mutex);
136
+ if (was_running)
137
+ pthread_join(pthread, NULL);
138
+ pthread = 0;
139
+ }
140
+ };
141
+
@@ -0,0 +1,72 @@
1
+ #ifndef SIGNAL_SAFE_SEMAPHORE_HH
2
+ #define SIGNAL_SAFE_SEMAPHORE_HH
3
+
4
+ #if defined(__APPLE__)
5
+ /* macOS */
6
+ #include <dispatch/dispatch.h>
7
+ #elif defined(__FreeBSD__)
8
+ /* FreeBSD */
9
+ #include <pthread_np.h>
10
+ #include <semaphore.h>
11
+ #else
12
+ /* Linux */
13
+ #include <semaphore.h>
14
+ #include <sys/syscall.h> /* for SYS_gettid */
15
+ #endif
16
+
17
+ // A basic semaphore built on sem_wait/sem_post
18
+ // post() is guaranteed to be async-signal-safe
19
+ class SignalSafeSemaphore {
20
+ #ifdef __APPLE__
21
+ dispatch_semaphore_t sem;
22
+ #else
23
+ sem_t sem;
24
+ #endif
25
+
26
+ public:
27
+
28
+ SignalSafeSemaphore(unsigned int value = 0) {
29
+ #ifdef __APPLE__
30
+ sem = dispatch_semaphore_create(value);
31
+ #else
32
+ sem_init(&sem, 0, value);
33
+ #endif
34
+ };
35
+
36
+ ~SignalSafeSemaphore() {
37
+ #ifdef __APPLE__
38
+ dispatch_release(sem);
39
+ #else
40
+ sem_destroy(&sem);
41
+ #endif
42
+ };
43
+
44
+ void wait() {
45
+ #ifdef __APPLE__
46
+ dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
47
+ #else
48
+ // Use sem_timedwait so that we get a crash instead of a deadlock for
49
+ // easier debugging
50
+ struct timespec ts = (TimeStamp::NowRealtime() + TimeStamp::from_seconds(5)).timespec();
51
+
52
+ int ret;
53
+ do {
54
+ ret = sem_timedwait(&sem, &ts);
55
+ } while (ret && errno == EINTR);
56
+ if (ret != 0) {
57
+ rb_bug("VERNIER: sem_timedwait waited over 5 seconds");
58
+ }
59
+ assert(ret == 0);
60
+ #endif
61
+ }
62
+
63
+ void post() {
64
+ #ifdef __APPLE__
65
+ dispatch_semaphore_signal(sem);
66
+ #else
67
+ sem_post(&sem);
68
+ #endif
69
+ }
70
+ };
71
+
72
+ #endif