pitchfork 0.12.0 → 0.14.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.
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
+ }