userfaultfd 1.0.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.
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserfaultFD
4
+ # Owns either a Ruby callback thread or a GVL-free native handler thread.
5
+ class Handler
6
+ attr_reader :error
7
+
8
+ def initialize(uffd, mode: nil, io: nil, source: nil, &block)
9
+ raise ArgumentError, "choose a block or mode, not both" if mode && block
10
+ raise ArgumentError, "a block or mode is required" unless mode || block
11
+
12
+ @uffd = uffd
13
+ if block
14
+ raise DeadlockError, "strict mode cannot verify raw pointer faults" if UserfaultFD.strict
15
+
16
+ warn "userfaultfd: Ruby handlers require Region#read/write or a separate faulting process"
17
+ start_ruby_handler(block)
18
+ else
19
+ native_source = mode == :backing_file ? io : source
20
+ @native = uffd.__send__(:start_native_handler, mode, native_source)
21
+ end
22
+ end
23
+
24
+ def stop
25
+ if @native
26
+ @native.stop
27
+ return self
28
+ end
29
+ return self unless @thread
30
+ raise Error, "handler cannot stop itself" if Thread.current == @thread
31
+
32
+ @stop_writer.write_nonblock(".") rescue nil
33
+ @thread.join
34
+ @thread = nil
35
+ raise @error if @error
36
+
37
+ self
38
+ end
39
+
40
+ def running?
41
+ @native ? @native.running? : !!@thread&.alive?
42
+ end
43
+
44
+ private
45
+
46
+ def start_ruby_handler(block)
47
+ @stop_reader, @stop_writer = IO.pipe
48
+ @event_reader = @uffd.__send__(:start_event_reader)
49
+ @event_io = IO.for_fd(@event_reader.fileno, autoclose: false)
50
+ @children = []
51
+ @thread = Thread.new do
52
+ loop do
53
+ ready = IO.select([@event_io, @stop_reader])&.first
54
+ break if ready&.include?(@stop_reader)
55
+
56
+ @event_reader.read_events.each do |event|
57
+ @children << event.child_uffd if event.is_a?(ForkEvent)
58
+ block.call(event)
59
+ end
60
+ end
61
+ rescue StandardError => e
62
+ @error = e
63
+ ensure
64
+ begin
65
+ @event_reader.stop
66
+ rescue StandardError => e
67
+ @error ||= e
68
+ end
69
+ @children.each(&:close)
70
+ @stop_reader.close
71
+ @stop_writer.close
72
+ end
73
+ @thread.report_on_exception = false
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UserfaultFD
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "userfaultfd/version"
4
+
5
+ # Linux userfaultfd descriptor and capability API.
6
+ #
7
+ # @!method self.supported?(user_mode_only: true)
8
+ # @param user_mode_only [Boolean] restrict faults to userspace; set false only
9
+ # on Linux before 5.11 when the caller has the required privilege
10
+ # @return [Boolean] whether a permitted userfaultfd can be initialized
11
+ # @!method self.features(user_mode_only: true)
12
+ # @return [Array<Symbol>] features available from the running kernel
13
+ # @!method initialize(features: nil, user_mode_only: true)
14
+ # @param features [Array<Symbol>, nil] features to require; by default fork
15
+ # events are attempted and omitted when the kernel denies permission
16
+ # @param user_mode_only [Boolean] restrict faults to userspace
17
+ # @!method register(region, mode:)
18
+ # @param region [UserfaultFD::Region]
19
+ # @param mode [Symbol, Array<Symbol>] `:missing`, `:wp`, or `:minor`
20
+ # @return [Integer] ioctl bitmask supported for the range
21
+ # @!method unregister(region)
22
+ # @return [nil]
23
+ # @!method enabled_features
24
+ # @return [Array<Symbol>] features enabled for this descriptor
25
+ class UserfaultFD
26
+ class Error < StandardError; end
27
+ class UnsupportedError < Error; end
28
+ class DeadlockError < Error; end
29
+
30
+ class << self
31
+ attr_accessor :strict
32
+ end
33
+
34
+ self.strict = false
35
+ end
36
+
37
+ require "userfaultfd/userfaultfd"
38
+ require_relative "userfaultfd/handler"
39
+
40
+ class UserfaultFD
41
+ def to_io
42
+ IO.for_fd(fileno, autoclose: false)
43
+ end
44
+
45
+ def start_handler(mode: nil, io: nil, source: nil, &block)
46
+ Handler.new(self, mode: mode, io: io, source: source, &block)
47
+ end
48
+ end
49
+
50
+ # A page-fault event. Resolution methods wake the faulting thread by default.
51
+ class UserfaultFD::Fault
52
+ def write? = flags.include?(:write)
53
+ def wp? = flags.include?(:wp)
54
+ def minor? = flags.include?(:minor)
55
+ end
56
+
57
+ # Compatibility with the constant generated by the original gem skeleton.
58
+ Userfaultfd = UserfaultFD
@@ -0,0 +1,93 @@
1
+ class UserfaultFD
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ class UnsupportedError < Error
8
+ end
9
+
10
+ class DeadlockError < Error
11
+ end
12
+
13
+ type feature = :pagefault_flag_wp | :event_fork | :event_remap | :event_remove | :event_unmap | :missing_hugetlbfs | :missing_shmem | :sigbus | :thread_id | :minor_hugetlbfs | :minor_shmem | :exact_address | :wp_hugetlbfs_shmem | :wp_unpopulated | :wp_async | :poison | :move
14
+ type register_mode = :missing | :wp | :minor
15
+ type native_mode = :zero_fill | :backing_file | :prefilled
16
+ type event = Fault | ForkEvent | RemapEvent | RemoveEvent | UnmapEvent
17
+
18
+ def self.supported?: (?user_mode_only: bool) -> bool
19
+ def self.features: (?user_mode_only: bool) -> Array[feature]
20
+ def self.strict: () -> bool
21
+ def self.strict=: (bool) -> bool
22
+
23
+ def initialize: (?features: Array[feature]?, ?user_mode_only: bool) -> void
24
+ def features: () -> Array[feature]
25
+ def enabled_features: () -> Array[feature]
26
+ def backend: () -> (:device | :syscall)
27
+ def fileno: () -> Integer
28
+ def to_io: () -> IO
29
+ def close: () -> nil
30
+ def closed?: () -> bool
31
+ def register: (Region, mode: register_mode | Array[register_mode]) -> Integer
32
+ def unregister: (Region) -> nil
33
+ def writeprotect: (Region, enabled: bool) -> nil
34
+ def read_events: (?Integer max) -> Array[event]
35
+ def start_handler: (mode: native_mode, ?io: IO?, ?source: Region?) -> Handler
36
+ | () { (event) -> void } -> Handler
37
+
38
+ class Region
39
+ def initialize: (size: Integer, ?shared: bool, ?huge: bool) -> void
40
+ def address: () -> Integer
41
+ def to_ptr: () -> Integer
42
+ def size: () -> Integer
43
+ def page_size: () -> Integer
44
+ def read: (Integer offset, Integer length) -> String
45
+ def write: (Integer offset, String data) -> Integer
46
+ def madvise: (:dontneed | :remove) -> nil
47
+ def unmap: () -> nil
48
+ def unmapped?: () -> bool
49
+ end
50
+
51
+ class Fault
52
+ attr_reader address: Integer
53
+ attr_reader flags: Array[:write | :wp | :minor]
54
+ attr_reader thread_id: Integer?
55
+ def write?: () -> bool
56
+ def wp?: () -> bool
57
+ def minor?: () -> bool
58
+ def copy: (String, ?wake: bool) -> Integer
59
+ def zero: (?wake: bool) -> Integer
60
+ def continue: (?wake: bool) -> Integer
61
+ def poison: (?wake: bool) -> Integer
62
+ def move: (Region source, ?offset: Integer, ?wake: bool) -> Integer
63
+ def wake: () -> nil
64
+ end
65
+
66
+ class ForkEvent
67
+ attr_reader child_uffd: UserfaultFD
68
+ end
69
+
70
+ class RemapEvent
71
+ attr_reader from: Integer
72
+ attr_reader to: Integer
73
+ attr_reader length: Integer
74
+ end
75
+
76
+ class RemoveEvent
77
+ attr_reader start: Integer
78
+ attr_reader end: Integer
79
+ end
80
+
81
+ class UnmapEvent
82
+ attr_reader start: Integer
83
+ attr_reader end: Integer
84
+ end
85
+
86
+ class Handler
87
+ attr_reader error: StandardError?
88
+ def stop: () -> self
89
+ def running?: () -> bool
90
+ end
91
+ end
92
+
93
+ Userfaultfd: singleton(UserfaultFD)
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: userfaultfd
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Handle Linux user-space page faults safely without blocking Ruby's GVL.
13
+ email:
14
+ - t.yudai92@gmail.com
15
+ executables: []
16
+ extensions:
17
+ - ext/userfaultfd/extconf.rb
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE.txt
21
+ - README.md
22
+ - docs/gvl-and-page-faults.md
23
+ - examples/dirty_tracking.rb
24
+ - examples/lazy_mmap.rb
25
+ - examples/oom_canary.rb
26
+ - examples/post_copy_migration.rb
27
+ - ext/userfaultfd/compat.h
28
+ - ext/userfaultfd/constants.c
29
+ - ext/userfaultfd/extconf.rb
30
+ - ext/userfaultfd/userfaultfd.c
31
+ - lib/userfaultfd.rb
32
+ - lib/userfaultfd/handler.rb
33
+ - lib/userfaultfd/version.rb
34
+ - sig/userfaultfd.rbs
35
+ homepage: https://github.com/ydah/userfaultfd
36
+ licenses:
37
+ - MIT
38
+ metadata:
39
+ allowed_push_host: https://rubygems.org
40
+ homepage_uri: https://github.com/ydah/userfaultfd
41
+ rubygems_mfa_required: 'true'
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.2.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 4.0.19
57
+ specification_version: 4
58
+ summary: Ruby bindings for Linux userfaultfd
59
+ test_files: []