pitchfork 0.12.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +7 -3
- data/.ruby-version +1 -0
- data/CHANGELOG.md +12 -0
- data/Dockerfile +1 -1
- data/Gemfile.lock +3 -5
- data/benchmark/README.md +1 -1
- data/benchmark/cow_benchmark.rb +1 -0
- data/docs/CONFIGURATION.md +39 -1
- data/docs/MIGRATING_FROM_UNICORN.md +34 -0
- data/docs/WHY_MIGRATE.md +5 -0
- data/examples/constant_caches.ru +1 -0
- data/examples/echo.ru +1 -0
- data/examples/hello.ru +1 -0
- data/examples/pitchfork.conf.minimal.rb +1 -0
- data/examples/pitchfork.conf.rb +1 -0
- data/examples/pitchfork.conf.service.rb +27 -0
- data/exe/pitchfork +5 -4
- data/ext/pitchfork_http/epollexclusive.h +2 -2
- data/ext/pitchfork_http/extconf.rb +3 -0
- data/ext/pitchfork_http/memory_page.c +223 -0
- data/ext/pitchfork_http/pitchfork_http.c +213 -211
- data/ext/pitchfork_http/pitchfork_http.rl +3 -1
- data/lib/pitchfork/children.rb +21 -15
- data/lib/pitchfork/configurator.rb +13 -0
- data/lib/pitchfork/const.rb +1 -0
- data/lib/pitchfork/flock.rb +1 -0
- data/lib/pitchfork/http_parser.rb +18 -72
- data/lib/pitchfork/http_response.rb +4 -3
- data/lib/pitchfork/http_server.rb +181 -62
- data/lib/pitchfork/launcher.rb +1 -0
- data/lib/pitchfork/message.rb +11 -6
- data/lib/pitchfork/select_waiter.rb +1 -0
- data/lib/pitchfork/shared_memory.rb +16 -14
- data/lib/pitchfork/socket_helper.rb +2 -1
- data/lib/pitchfork/stream_input.rb +6 -5
- data/lib/pitchfork/tee_input.rb +3 -2
- data/lib/pitchfork/tmpio.rb +1 -0
- data/lib/pitchfork/version.rb +1 -1
- data/lib/pitchfork/worker.rb +44 -15
- data/lib/pitchfork.rb +1 -20
- data/pitchfork.gemspec +0 -1
- metadata +7 -18
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d3eb42c934de40ea4ae4e25e5d334af536a03f9818dfec8798319744d1629a5
|
4
|
+
data.tar.gz: 5ea480e75dabff8298cb419a9ca1ba159af04985f4e02df8fcb51c6eff65b996
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c934f51445bc2b2904f9ecb84edfb0d454e87494fdb3a58210bf5e9b3e69887a6817be1d38f217696432ab40006448f97d22aa0eca866be4e09c6e80ca0499db
|
7
|
+
data.tar.gz: c2dfdf19c3a08aa91199b6289fef08add17281d60eaa3eefd361fb1de74aae0083ae2f10123293782d92b3c8a152b00c21676143c21b03487fecef2c8994d982
|
data/.github/workflows/ci.yml
CHANGED
@@ -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
data/Gemfile.lock
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pitchfork (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.
|
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.
|
32
|
+
2.3.27
|
data/benchmark/README.md
CHANGED
data/benchmark/cow_benchmark.rb
CHANGED
data/docs/CONFIGURATION.md
CHANGED
@@ -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
|
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.
|
data/examples/constant_caches.ru
CHANGED
data/examples/echo.ru
CHANGED
data/examples/hello.ru
CHANGED
data/examples/pitchfork.conf.rb
CHANGED
@@ -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
|
-
#
|
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", "
|
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
|
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
|
+
}
|