pitchfork 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +7 -3
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +12 -0
  5. data/Dockerfile +1 -1
  6. data/Gemfile.lock +3 -5
  7. data/benchmark/README.md +1 -1
  8. data/benchmark/cow_benchmark.rb +1 -0
  9. data/docs/CONFIGURATION.md +39 -1
  10. data/docs/MIGRATING_FROM_UNICORN.md +34 -0
  11. data/docs/WHY_MIGRATE.md +5 -0
  12. data/examples/constant_caches.ru +1 -0
  13. data/examples/echo.ru +1 -0
  14. data/examples/hello.ru +1 -0
  15. data/examples/pitchfork.conf.minimal.rb +1 -0
  16. data/examples/pitchfork.conf.rb +1 -0
  17. data/examples/pitchfork.conf.service.rb +27 -0
  18. data/exe/pitchfork +5 -4
  19. data/ext/pitchfork_http/epollexclusive.h +2 -2
  20. data/ext/pitchfork_http/extconf.rb +3 -0
  21. data/ext/pitchfork_http/memory_page.c +223 -0
  22. data/ext/pitchfork_http/pitchfork_http.c +213 -211
  23. data/ext/pitchfork_http/pitchfork_http.rl +3 -1
  24. data/lib/pitchfork/children.rb +21 -15
  25. data/lib/pitchfork/configurator.rb +13 -0
  26. data/lib/pitchfork/const.rb +1 -0
  27. data/lib/pitchfork/flock.rb +1 -0
  28. data/lib/pitchfork/http_parser.rb +18 -72
  29. data/lib/pitchfork/http_response.rb +4 -3
  30. data/lib/pitchfork/http_server.rb +181 -62
  31. data/lib/pitchfork/launcher.rb +1 -0
  32. data/lib/pitchfork/message.rb +11 -6
  33. data/lib/pitchfork/select_waiter.rb +1 -0
  34. data/lib/pitchfork/shared_memory.rb +16 -14
  35. data/lib/pitchfork/socket_helper.rb +2 -1
  36. data/lib/pitchfork/stream_input.rb +6 -5
  37. data/lib/pitchfork/tee_input.rb +3 -2
  38. data/lib/pitchfork/tmpio.rb +1 -0
  39. data/lib/pitchfork/version.rb +1 -1
  40. data/lib/pitchfork/worker.rb +44 -15
  41. data/lib/pitchfork.rb +1 -20
  42. data/pitchfork.gemspec +0 -1
  43. metadata +7 -18
  44. data/lib/pitchfork/app/old_rails/static.rb +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7bf3d38329b727807923b9d57bfa09f686e313e30b374542f703b9b27f8f6afb
4
- data.tar.gz: b47932131e9873ac74617d9cabdd99d2d7c948dcd03564a544b51bd0449ad54c
3
+ metadata.gz: 9d3eb42c934de40ea4ae4e25e5d334af536a03f9818dfec8798319744d1629a5
4
+ data.tar.gz: 5ea480e75dabff8298cb419a9ca1ba159af04985f4e02df8fcb51c6eff65b996
5
5
  SHA512:
6
- metadata.gz: d44f02f6db020cb66ccf17e7221bf86fbec3da47f2ddb9a3d00da5a857472e512f490b02e60f8394a95b595074ab86aa33f2d0822dab45f60090fc355a97bee9
7
- data.tar.gz: 868ae5172e8bcee925528a12d3d332c40fb88fd2d6ca1394273ddb5cbe1fde92b0e17ace1abd3681620abffb325a381d6c6622885cdf21f965ade000180bc890
6
+ metadata.gz: c934f51445bc2b2904f9ecb84edfb0d454e87494fdb3a58210bf5e9b3e69887a6817be1d38f217696432ab40006448f97d22aa0eca866be4e09c6e80ca0499db
7
+ data.tar.gz: c2dfdf19c3a08aa91199b6289fef08add17281d60eaa3eefd361fb1de74aae0083ae2f10123293782d92b3c8a152b00c21676143c21b03487fecef2c8994d982
@@ -4,13 +4,17 @@ on: [push, pull_request]
4
4
 
5
5
  jobs:
6
6
  ruby:
7
- name: Ruby ${{ matrix.ruby }}
7
+ name: Ruby ${{ matrix.ruby }} ${{ matrix.rubyopt }}
8
8
  timeout-minutes: 15
9
9
  strategy:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: ["ubuntu-latest"]
13
13
  ruby: ["ruby-head", "3.3", "3.2", "3.1", "3.0", "2.7", "2.6"]
14
+ rubyopt: [""]
15
+ include:
16
+ - ruby: "3.3"
17
+ rubyopt: "--enable-frozen-string-literal"
14
18
  runs-on: ubuntu-latest
15
19
  steps:
16
20
  - name: Check out code
@@ -25,5 +29,5 @@ jobs:
25
29
  - name: Install packages
26
30
  run: sudo apt-get install -y ragel socat netcat
27
31
 
28
- - name: Tests
29
- run: bundle exec rake
32
+ - name: Tests ${{ matrix.rubyopt }}
33
+ run: RUBYOPT="${{ matrix.rubyopt }}" bundle exec rake
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Unreleased
2
2
 
3
+ # 0.14.0
4
+
5
+ - Remove the dependency on `raindrops`.
6
+ - Add `X-Request-Id` header in the workers proctitle if present.
7
+ - Added experimental service workers.
8
+
9
+ # 0.13.0
10
+
11
+ - Fix compatibility with `--enable-frozen-string-literal` in preparation for Ruby 3.4.
12
+ - Extend timed out workers deadline after sending signals to avoid flooding.
13
+ - Fix compilation with Ruby 2.7 and older on macOS.
14
+
3
15
  # 0.12.0
4
16
 
5
17
  - Disable IO tracking on Rubies older than 3.2.3 to avoid running into https://bugs.ruby-lang.org/issues/19531.
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bookworm
1
+ FROM mcr.microsoft.com/devcontainers/ruby:3.3-bookworm
2
2
  RUN apt-get update -y && apt-get install -y ragel socat netcat-traditional smem apache2-utils
3
3
  WORKDIR /app
4
4
  CMD [ "bash" ]
data/Gemfile.lock CHANGED
@@ -1,9 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pitchfork (0.12.0)
4
+ pitchfork (0.14.0)
5
5
  rack (>= 2.0)
6
- raindrops (~> 0.7)
7
6
 
8
7
  GEM
9
8
  remote: https://rubygems.org/
@@ -12,8 +11,7 @@ GEM
12
11
  nio4r (2.7.0)
13
12
  puma (6.4.2)
14
13
  nio4r (~> 2.0)
15
- rack (3.0.9.1)
16
- raindrops (0.20.1)
14
+ rack (3.0.11)
17
15
  rake (13.0.6)
18
16
  rake-compiler (1.2.1)
19
17
  rake
@@ -31,4 +29,4 @@ DEPENDENCIES
31
29
  rake-compiler
32
30
 
33
31
  BUNDLED WITH
34
- 2.3.22
32
+ 2.3.27
data/benchmark/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Copy on Write Efficiency
4
4
 
5
- This benchmark aimed to compare real memory usage of differnet servers.
5
+ This benchmark aimed to compare real memory usage of different servers.
6
6
 
7
7
  For instance, Puma 2 workers + 2 threads:
8
8
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  require "net/http"
3
4
 
4
5
  app_path = File.expand_path('../examples/constant_caches.ru', __dir__)
@@ -188,7 +188,7 @@ The second "hard" timeout, is the sum of `timeout` and `cleanup`.
188
188
  Workers taking longer than this time period to be ready to handle a new
189
189
  request will be forcibly killed (via `SIGKILL`).
190
190
 
191
- Neither of these timeout mecanisms should be routinely relied on, and should
191
+ Neither of these timeout mechanisms should be routinely relied on, and should
192
192
  instead be considered as a last line of defense in case you application
193
193
  is impacted by bugs causing unexpectedly slow response time, or fully stuck
194
194
  processes.
@@ -299,8 +299,13 @@ end
299
299
  ```ruby
300
300
  after_mold_fork do |server, mold|
301
301
  Database.disconnect!
302
+
303
+ # Ruby < 3.3
302
304
  3.times { GC.start } # promote surviving objects to oldgen
303
305
  GC.compact
306
+
307
+ # Ruby >= 3.3
308
+ Process.warmup
304
309
  end
305
310
  ```
306
311
 
@@ -417,6 +422,39 @@ after_request_complete do |server, worker, env|
417
422
  end
418
423
  ```
419
424
 
425
+ ### `before_service_worker_ready` (experimental)
426
+
427
+ Experimental and may change at any point.
428
+
429
+ If defined, Pitchfork will spawn one extra worker, called a service worker
430
+ which doesn't accept incoming requests, but allows to perform service tasks
431
+ such as warming node local caches or emitting metrics.
432
+
433
+ Service workers are never promoted to molds, so it is safe to use threads and
434
+ other fork unsafe APIs.
435
+
436
+ This callback MUST not block. It should start one or multiple background threads
437
+ to perform tasks at regular intervals.
438
+
439
+ ```ruby
440
+ before_service_worker_ready do |server, service_worker|
441
+ Thread.new do
442
+ loop do
443
+ MyApp.emit_utilization_metrics
444
+ sleep 1
445
+ end
446
+ end
447
+ end
448
+ ```
449
+
450
+ ### `before_service_worker_exit` (experimental)
451
+
452
+ Experimental and may change at any point.
453
+
454
+ Optional.
455
+
456
+ Called whenever the service worker is exiting. This allow to do a clean shutdown.
457
+
420
458
  ## Reforking
421
459
 
422
460
  ### `refork_after`
@@ -0,0 +1,34 @@
1
+ # Unicorn Migration Guide
2
+
3
+ While Pitchfork started out as a patch on top of Unicorn, many Unicorn features
4
+ that don't make sense in containerized environments were removed to simplify the codebase.
5
+
6
+ This guide is intended to cover the most common changes you need to make to your configuration
7
+ in order to make the switch.
8
+
9
+ > [!NOTE]
10
+ > This document doesn't contain every incompatibility with Unicorn. If you encounter
11
+ additional incompatibilities, please open an Issue or a Pull Request to add your findings.
12
+
13
+ * The configurations `user`, `working_directory`, `stderr_path`, `stdout_path`, and `pid`
14
+ have been removed without replacement. Pitchfork is designed for modern deployment strategies
15
+ like Docker and Systemd, as such the responsibility for this functionality is delegated to
16
+ these systems.
17
+
18
+ * The configuration `preload_app` has been removed without replacement. Pitchfork will always behave
19
+ as if it is set to `true`.
20
+
21
+ * The Signal `USR2` has been repurposed for reforking. Remove `ExecReload` from your Sytemd unit
22
+ file, if it contains it. Reloading is not a supported feature of Pitchfork.
23
+
24
+ * The configuration `after_fork` has been split between `after_worker_fork` and `after_mold_fork`.
25
+
26
+ * If you use `unicorn-worker-killer` or similar gems, you will need to implement this functionally yourself since
27
+ there is no `pitchfork-worker-killer`. Changes to Pitchfork internals make this a pretty painless
28
+ ordeal, you can check out the following GitHub issue to get started: https://github.com/Shopify/pitchfork/issues/92
29
+
30
+ ## Reforking
31
+
32
+ [Reforking](REFORKING.md) is Pitchfork's main selling point. Give [Refork Safety](FORK_SAFETY.md#refork-safety) a read to understand
33
+ if your application may be compatible. [Enabling reforking](CONFIGURATION.md#refork_after) will give you memory savings above what Unicorn
34
+ is able to offer with its forking model.
data/docs/WHY_MIGRATE.md CHANGED
@@ -39,6 +39,11 @@ pid file management, hot reload have been stripped.
39
39
 
40
40
  Pitchfork only kept features that makes sense in a containerized world.
41
41
 
42
+ ### Migration Guide
43
+
44
+ If the above points convinced you to make the switch, take a look at the [migration guide](MIGRATING_FROM_UNICORN.md).
45
+ It will go over the most common changes you will need to make to use Pitchfork.
46
+
42
47
  ## Coming from Puma
43
48
 
44
49
  Generally speaking, compared to (threaded) Puma, Pitchfork *may* offer better latency and isolation at the expense of throughput.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require_relative "../lib/pitchfork/mem_info"
2
3
 
3
4
  module App
data/examples/echo.ru CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Example application that echoes read data back to the HTTP client.
2
3
  # This emulates the old echo protocol people used to run.
3
4
  #
data/examples/hello.ru CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  run lambda { |env|
2
3
  /\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [100, {}, []]
3
4
  body = "Hello World!\n"
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Minimal sample configuration file for Pitchfork
2
3
 
3
4
  # listen 2007 # by default Pitchfork listens on port 8080
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # Sample verbose configuration file for Pitchfork
2
3
 
3
4
  # Use at least one worker per core if you're on a dedicated server,
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ # Minimal sample configuration file for Pitchfork
3
+
4
+ # listen 2007 # by default Pitchfork listens on port 8080
5
+ worker_processes 4 # this should be >= nr_cpus
6
+ refork_after [50, 100, 1000]
7
+
8
+ service_thread = nil
9
+ service_shutdown = false
10
+
11
+ before_service_worker_ready do |server, service|
12
+ service_thread = Thread.new do
13
+ server.logger.info "Service: start"
14
+ count = 1
15
+ until service_shutdown
16
+ server.logger.info "Service: ping count=#{count}"
17
+ count += 1
18
+ sleep 1
19
+ end
20
+ end
21
+ end
22
+
23
+ before_service_worker_exit do |server, service|
24
+ server.logger.info "Service: shutting down"
25
+ service_shutdown = true
26
+ service_thread&.join(2)
27
+ end
data/exe/pitchfork CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
  # -*- encoding: binary -*-
3
4
  require 'pitchfork/launcher'
4
5
  require 'optparse'
@@ -63,7 +64,7 @@ op = OptionParser.new("", 24, ' ') do |opts|
63
64
  warn "-s/--server only exists for compatibility with rackup"
64
65
  end
65
66
 
66
- # Unicorn-specific stuff
67
+ # Pitchfork-specific stuff
67
68
  opts.on("-l", "--listen {HOST:PORT|PATH}",
68
69
  "listen on HOST:PORT or PATH",
69
70
  "this may be specified multiple times",
@@ -71,11 +72,11 @@ op = OptionParser.new("", 24, ' ') do |opts|
71
72
  options[:listeners] << address
72
73
  end
73
74
 
74
- opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f|
75
+ opts.on("-c", "--config-file FILE", "Pitchfork-specific config file") do |f|
75
76
  options[:config_file] = f
76
77
  end
77
78
 
78
- # I'm avoiding Unicorn-specific config options on the command-line.
79
+ # I'm avoiding Pitchfork-specific config options on the command-line.
79
80
  # IMNSHO, config options on the command-line are redundant given
80
81
  # config files and make things unnecessarily complicated with multiple
81
82
  # places to look for a config option.
@@ -88,7 +89,7 @@ op = OptionParser.new("", 24, ' ') do |opts|
88
89
  end
89
90
 
90
91
  opts.on_tail("-v", "--version", "Show version") do
91
- puts "#{cmd} v#{Pitchfork::Const::UNICORN_VERSION}"
92
+ puts "#{cmd} v#{Pitchfork::VERSION} (based on Unicorn v#{Pitchfork::Const::UNICORN_VERSION})"
92
93
  exit
93
94
  end
94
95
 
@@ -11,10 +11,11 @@
11
11
  #if defined(HAVE_EPOLL_CREATE1)
12
12
  # include <sys/epoll.h>
13
13
  # include <errno.h>
14
- # include <ruby/io.h>
15
14
  # include <ruby/thread.h>
16
15
  #endif /* __linux__ */
17
16
 
17
+ #include <ruby/io.h>
18
+
18
19
  #if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1)
19
20
  # define USE_EPOLL (1)
20
21
  #else
@@ -50,7 +51,6 @@ static VALUE prep_readers(VALUE cls, VALUE readers)
50
51
  for (i = 0; i < RARRAY_LEN(readers); i++) {
51
52
  int rc;
52
53
  struct epoll_event e;
53
- rb_io_t *fptr;
54
54
  VALUE io = rb_ary_entry(readers, i);
55
55
 
56
56
  e.data.u64 = i; /* the reason readers shouldn't change */
@@ -1,9 +1,12 @@
1
1
  # -*- encoding: binary -*-
2
+ # frozen_string_literal: true
2
3
  require 'mkmf'
3
4
 
5
+ append_cflags("-fvisibility=hidden")
4
6
  have_const("PR_SET_CHILD_SUBREAPER", "sys/prctl.h")
5
7
  have_func("rb_enc_interned_str", "ruby.h") # Ruby 3.0+
6
8
  have_func("rb_io_descriptor", "ruby.h") # Ruby 3.1+
9
+ have_func("getpagesize", "unistd.h")
7
10
 
8
11
  if RUBY_VERSION.start_with?('3.0.')
9
12
  # https://bugs.ruby-lang.org/issues/18772
@@ -0,0 +1,223 @@
1
+ /* Note: A large part of this code has been borrowed/stolen/adapted from raindrops. */
2
+
3
+ #include <ruby.h>
4
+ #include <unistd.h>
5
+ #include <sys/mman.h>
6
+ #include <errno.h>
7
+ #include <stddef.h>
8
+ #include <string.h>
9
+ #include <assert.h>
10
+
11
+ #define PAGE_MASK (~(page_size - 1))
12
+ #define PAGE_ALIGN(addr) (((addr) + page_size - 1) & PAGE_MASK)
13
+
14
+ static size_t slot_size = 128;
15
+
16
+ static void init_slot_size(void)
17
+ {
18
+ long tmp = 2;
19
+
20
+ #ifdef _SC_NPROCESSORS_CONF
21
+ tmp = sysconf(_SC_NPROCESSORS_CONF);
22
+ #endif
23
+ /* no point in padding on single CPU machines */
24
+ if (tmp == 1) {
25
+ slot_size = sizeof(unsigned long);
26
+ }
27
+ #ifdef _SC_LEVEL1_DCACHE_LINESIZE
28
+ if (tmp != 1) {
29
+ tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
30
+ if (tmp > 0) {
31
+ slot_size = (size_t)tmp;
32
+ }
33
+ }
34
+ #endif
35
+ }
36
+
37
+ static size_t page_size = (size_t)-1;
38
+
39
+ static void init_page_size(void)
40
+ {
41
+ #if defined(_SC_PAGE_SIZE)
42
+ page_size = (size_t)sysconf(_SC_PAGE_SIZE);
43
+ #elif defined(_SC_PAGESIZE)
44
+ page_size = (size_t)sysconf(_SC_PAGESIZE);
45
+ #elif defined(HAVE_GETPAGESIZE)
46
+ page_size = (size_t)getpagesize();
47
+ #elif defined(PAGE_SIZE)
48
+ page_size = (size_t)PAGE_SIZE;
49
+ #elif defined(PAGESIZE)
50
+ page_size = (size_t)PAGESIZE;
51
+ #else
52
+ # error unable to detect page size for mmap()
53
+ #endif
54
+ if ((page_size == (size_t)-1) || (page_size < slot_size)) {
55
+ rb_raise(rb_eRuntimeError, "system page size invalid: %llu", (unsigned long long)page_size);
56
+ }
57
+ }
58
+
59
+ /* each slot is a counter */
60
+ struct slot {
61
+ unsigned long counter;
62
+ } __attribute__((packed));
63
+
64
+ /* allow mmap-ed regions to store more than one counter */
65
+ struct memory_page {
66
+ size_t size;
67
+ size_t capa;
68
+ struct slot *slots;
69
+ };
70
+
71
+ static void memory_page_free(void *ptr)
72
+ {
73
+ struct memory_page *page = (struct memory_page *)ptr;
74
+
75
+ if (page->slots != MAP_FAILED) {
76
+ int rv = munmap(page->slots, slot_size * page->capa);
77
+ if (rv != 0) {
78
+ rb_bug("Pitchfork::MemoryPage munmap failed in gc: %s", strerror(errno));
79
+ }
80
+ }
81
+
82
+ xfree(ptr);
83
+ }
84
+
85
+ static size_t memory_page_memsize(const void *ptr)
86
+ {
87
+ const struct memory_page *page = (const struct memory_page *)ptr;
88
+ size_t memsize = sizeof(struct memory_page);
89
+ if (page->slots != MAP_FAILED) {
90
+ memsize += slot_size * page->capa;
91
+ }
92
+ return memsize;
93
+ }
94
+
95
+ static const rb_data_type_t memory_page_type = {
96
+ .wrap_struct_name = "Pitchfork::MemoryPage",
97
+ .function = {
98
+ .dmark = NULL,
99
+ .dfree = memory_page_free,
100
+ .dsize = memory_page_memsize,
101
+ },
102
+ .flags = RUBY_TYPED_WB_PROTECTED,
103
+ };
104
+
105
+ static VALUE memory_page_alloc(VALUE klass)
106
+ {
107
+ struct memory_page *page;
108
+ VALUE obj = TypedData_Make_Struct(klass, struct memory_page, &memory_page_type, page);
109
+
110
+ page->slots = MAP_FAILED;
111
+ return obj;
112
+ }
113
+
114
+ static struct memory_page *memory_page_get(VALUE self)
115
+ {
116
+ struct memory_page *page;
117
+
118
+ TypedData_Get_Struct(self, struct memory_page, &memory_page_type, page);
119
+
120
+ if (page->slots == MAP_FAILED) {
121
+ rb_raise(rb_eStandardError, "invalid or freed Pitchfork::MemoryPage");
122
+ }
123
+
124
+ return page;
125
+ }
126
+
127
+ static unsigned long *memory_page_address(VALUE self, VALUE index)
128
+ {
129
+ struct memory_page *page = memory_page_get(self);
130
+ unsigned long off = FIX2ULONG(index) * slot_size;
131
+
132
+ if (off >= slot_size * page->size) {
133
+ rb_raise(rb_eArgError, "offset overrun");
134
+ }
135
+
136
+ return (unsigned long *)((unsigned long)page->slots + off);
137
+ }
138
+
139
+
140
+ static VALUE memory_page_aref(VALUE self, VALUE index)
141
+ {
142
+ return ULONG2NUM(*memory_page_address(self, index));
143
+ }
144
+
145
+ static VALUE memory_page_aset(VALUE self, VALUE index, VALUE value)
146
+ {
147
+ unsigned long *addr = memory_page_address(self, index);
148
+ *addr = NUM2ULONG(value);
149
+ return value;
150
+ }
151
+
152
+ static VALUE memory_page_initialize(VALUE self, VALUE size)
153
+ {
154
+ struct memory_page *page;
155
+ TypedData_Get_Struct(self, struct memory_page, &memory_page_type, page);
156
+
157
+ int tries = 1;
158
+
159
+ if (page->slots != MAP_FAILED) {
160
+ rb_raise(rb_eRuntimeError, "already initialized");
161
+ }
162
+
163
+ page->size = NUM2SIZET(size);
164
+ if (page->size < 1) {
165
+ rb_raise(rb_eArgError, "size must be >= 1");
166
+ }
167
+
168
+ size_t tmp = PAGE_ALIGN(slot_size * page->size);
169
+ page->capa = tmp / slot_size;
170
+ assert(PAGE_ALIGN(slot_size * page->capa) == tmp && "not aligned");
171
+
172
+ retry:
173
+ page->slots = mmap(NULL, tmp, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
174
+
175
+ if (page->slots == MAP_FAILED) {
176
+ int err = errno;
177
+
178
+ if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) {
179
+ rb_gc();
180
+ goto retry;
181
+ }
182
+ rb_sys_fail("mmap");
183
+ }
184
+
185
+ memset(page->slots, 0, tmp);
186
+
187
+ return self;
188
+ }
189
+
190
+ void init_pitchfork_memory_page(VALUE mPitchfork)
191
+ {
192
+ init_slot_size();
193
+ init_page_size();
194
+
195
+ VALUE rb_cMemoryPage = rb_define_class_under(mPitchfork, "MemoryPage", rb_cObject);
196
+
197
+ /*
198
+ * The size of one page of memory for a mmap()-ed MemoryPage region.
199
+ * Typically 4096 bytes under Linux.
200
+ */
201
+ rb_define_const(rb_cMemoryPage, "PAGE_SIZE", SIZET2NUM(page_size));
202
+
203
+ /*
204
+ * The size (in bytes) of a slot in a MemoryPage object.
205
+ * This is the size of a word on single CPU systems and
206
+ * the size of the L1 cache line size if detectable.
207
+ *
208
+ * Defaults to 128 bytes if undetectable.
209
+ */
210
+ rb_define_const(rb_cMemoryPage, "SLOT_SIZE", SIZET2NUM(slot_size));
211
+
212
+ rb_define_const(rb_cMemoryPage, "SLOTS", SIZET2NUM(page_size / slot_size));
213
+
214
+ /*
215
+ * The maximum value a slot counter can hold
216
+ */
217
+ rb_define_const(rb_cMemoryPage, "SLOT_MAX", ULONG2NUM((unsigned long)-1));
218
+
219
+ rb_define_alloc_func(rb_cMemoryPage, memory_page_alloc);
220
+ rb_define_private_method(rb_cMemoryPage, "initialize", memory_page_initialize, 1);
221
+ rb_define_method(rb_cMemoryPage, "[]", memory_page_aref, 1);
222
+ rb_define_method(rb_cMemoryPage, "[]=", memory_page_aset, 2);
223
+ }