io-event 1.5.1 → 1.6.5

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: 92dd9a674f53250bb7307226c47bfa3fc4853c8a363509fc02d99a23fcd39194
4
- data.tar.gz: 6aca8cca51546cccb4696c4744e2be05b251cb81058bc9c44ca1deb42bb1930a
3
+ metadata.gz: 53b1f32affa83ac713eaaf8fba4b9a1c9d2f25421908759dde9b5adec11c498f
4
+ data.tar.gz: 6504222eab182d55f2b56ef91c4de9f301f509a69fff17f286c03f0ef0315786
5
5
  SHA512:
6
- metadata.gz: b12806d9e7e9c184bec13d81792f4f77ed68162e90ada118e89015aeba5571a66f6a8aa7395d40bf923c95d61438c4121cd238433ca6b3874320b60a44d09929
7
- data.tar.gz: 27c2c7d7de7b94bddb174298996192495f8edab54b42ed58cb54f61623210504db5101924dda15e6fd7dc54df81d6fdbaff44ca42608c662febba2c17d260555
6
+ metadata.gz: ac5f863272fb2a532d1adbe16e2960f78dd6f4ce52891e391f301d40bb28ad74e2775348d55610f1f308f7a69b0b362e93f794b44fe94b4ec6e91331a2e851d5
7
+ data.tar.gz: 042a1a3ba4ec1a3e438d823b9d9348eeebc86be2464030744a9a01ff50654aeeb229fb79729083d3f1bdb2feb968dcee8ec320d33de7083196eb1afd887c5ef6
checksums.yaml.gz.sig CHANGED
Binary file
data/ext/extconf.rb CHANGED
@@ -39,6 +39,8 @@ if have_header('sys/event.h')
39
39
  $srcs << "io/event/selector/kqueue.c"
40
40
  end
41
41
 
42
+ have_header('sys/wait.h')
43
+
42
44
  have_header('sys/eventfd.h')
43
45
  $srcs << "io/event/interrupt.c"
44
46
 
@@ -152,6 +152,7 @@ void close_internal(struct IO_Event_Selector_EPoll *selector)
152
152
  IO_Event_Interrupt_close(&selector->interrupt);
153
153
  }
154
154
  }
155
+
155
156
  static
156
157
  void IO_Event_Selector_EPoll_Type_free(void *_selector)
157
158
  {
@@ -785,24 +786,21 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
785
786
  return NULL;
786
787
  }
787
788
 
788
- if (FIXNUM_P(duration)) {
789
+ if (RB_INTEGER_TYPE_P(duration)) {
789
790
  storage->tv_sec = NUM2TIMET(duration);
790
791
  storage->tv_nsec = 0;
791
792
 
792
793
  return storage;
793
794
  }
794
795
 
795
- else if (RB_FLOAT_TYPE_P(duration)) {
796
- double value = RFLOAT_VALUE(duration);
797
- time_t seconds = value;
798
-
799
- storage->tv_sec = seconds;
800
- storage->tv_nsec = (value - seconds) * 1000000000L;
801
-
802
- return storage;
803
- }
796
+ duration = rb_to_float(duration);
797
+ double value = RFLOAT_VALUE(duration);
798
+ time_t seconds = value;
799
+
800
+ storage->tv_sec = seconds;
801
+ storage->tv_nsec = (value - seconds) * 1000000000L;
804
802
 
805
- rb_raise(rb_eRuntimeError, "unable to convert timeout");
803
+ return storage;
806
804
  }
807
805
 
808
806
  static
@@ -371,7 +371,7 @@ VALUE IO_Event_Selector_KQueue_loop(VALUE self) {
371
371
 
372
372
  VALUE IO_Event_Selector_KQueue_idle_duration(VALUE self) {
373
373
  struct IO_Event_Selector_KQueue *selector = NULL;
374
- TypedData_Get_Struct(self, struct IO_Event_Selector_EPoll, &IO_Event_Selector_KQueue_Type, selector);
374
+ TypedData_Get_Struct(self, struct IO_Event_Selector_KQueue, &IO_Event_Selector_KQueue_Type, selector);
375
375
 
376
376
  double duration = selector->idle_duration.tv_sec + (selector->idle_duration.tv_nsec / 1000000000.0);
377
377
 
@@ -799,24 +799,21 @@ struct timespec * make_timeout(VALUE duration, struct timespec * storage) {
799
799
  return NULL;
800
800
  }
801
801
 
802
- if (FIXNUM_P(duration)) {
802
+ if (RB_INTEGER_TYPE_P(duration)) {
803
803
  storage->tv_sec = NUM2TIMET(duration);
804
804
  storage->tv_nsec = 0;
805
805
 
806
806
  return storage;
807
807
  }
808
808
 
809
- else if (RB_FLOAT_TYPE_P(duration)) {
810
- double value = RFLOAT_VALUE(duration);
811
- time_t seconds = value;
812
-
813
- storage->tv_sec = seconds;
814
- storage->tv_nsec = (value - seconds) * 1000000000L;
815
-
816
- return storage;
817
- }
809
+ duration = rb_to_float(duration);
810
+ double value = RFLOAT_VALUE(duration);
811
+ time_t seconds = value;
812
+
813
+ storage->tv_sec = seconds;
814
+ storage->tv_nsec = (value - seconds) * 1000000000L;
818
815
 
819
- rb_raise(rb_eRuntimeError, "unable to convert timeout");
816
+ return storage;
820
817
  }
821
818
 
822
819
  static
@@ -34,8 +34,9 @@
34
34
  #endif
35
35
 
36
36
  #include <time.h>
37
- #ifdef HAVE_RB_PROCESS_STATUS_WAIT
38
- #include <sys/wait.h>
37
+
38
+ #ifdef HAVE_SYS_WAIT_H
39
+ #include <sys/wait.h>
39
40
  #endif
40
41
 
41
42
  enum IO_Event {
@@ -901,24 +901,21 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
901
901
  return NULL;
902
902
  }
903
903
 
904
- if (FIXNUM_P(duration)) {
904
+ if (RB_INTEGER_TYPE_P(duration)) {
905
905
  storage->tv_sec = NUM2TIMET(duration);
906
906
  storage->tv_nsec = 0;
907
907
 
908
908
  return storage;
909
909
  }
910
910
 
911
- else if (RB_FLOAT_TYPE_P(duration)) {
912
- double value = RFLOAT_VALUE(duration);
913
- time_t seconds = value;
914
-
915
- storage->tv_sec = seconds;
916
- storage->tv_nsec = (value - seconds) * 1000000000L;
917
-
918
- return storage;
919
- }
911
+ duration = rb_to_float(duration);
912
+ double value = RFLOAT_VALUE(duration);
913
+ time_t seconds = value;
914
+
915
+ storage->tv_sec = seconds;
916
+ storage->tv_nsec = (value - seconds) * 1000000000L;
920
917
 
921
- rb_raise(rb_eRuntimeError, "unable to convert timeout");
918
+ return storage;
922
919
  }
923
920
 
924
921
  static
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021, by Wander Hillen.
5
+ # Copyright, 2021-2024, by Samuel Williams.
6
+
7
+ class IO
8
+ module Event
9
+ # A priority queue implementation using a standard binary minheap. It uses straight comparison
10
+ # of its contents to determine priority.
11
+ # See <https://en.wikipedia.org/wiki/Binary_heap> for explanations of the main methods.
12
+ class PriorityHeap
13
+ def initialize
14
+ # The heap is represented with an array containing a binary tree. See
15
+ # https://en.wikipedia.org/wiki/Binary_heap#Heap_implementation for how this array
16
+ # is built up.
17
+ @contents = []
18
+ end
19
+
20
+ # Returns the earliest timer or nil if the heap is empty.
21
+ def peek
22
+ @contents[0]
23
+ end
24
+
25
+ # Returns the number of elements in the heap
26
+ def size
27
+ @contents.size
28
+ end
29
+
30
+ # Returns the earliest timer if the heap is non-empty and removes it from the heap.
31
+ # Returns nil if the heap is empty. (and doesn't change the heap in that case)
32
+ def pop
33
+ # If the heap is empty:
34
+ if @contents.empty?
35
+ return nil
36
+ end
37
+
38
+ # If we have only one item, no swapping is required:
39
+ if @contents.size == 1
40
+ return @contents.pop
41
+ end
42
+
43
+ # Take the root of the tree:
44
+ value = @contents[0]
45
+
46
+ # Remove the last item in the tree:
47
+ last = @contents.pop
48
+
49
+ # Overwrite the root of the tree with the item:
50
+ @contents[0] = last
51
+
52
+ # Bubble it down into place:
53
+ bubble_down(0)
54
+
55
+ # validate!
56
+
57
+ return value
58
+ end
59
+
60
+ # Inserts a new timer into the heap, then rearranges elements until the heap invariant is true again.
61
+ def push(element)
62
+ # Insert the item at the end of the heap:
63
+ @contents.push(element)
64
+
65
+ # Bubble it up into position:
66
+ bubble_up(@contents.size - 1)
67
+
68
+ # validate!
69
+
70
+ return self
71
+ end
72
+
73
+ # Empties out the heap, discarding all elements
74
+ def clear!
75
+ @contents = []
76
+ end
77
+
78
+ # Validate the heap invariant. Every element except the root must not be smaller than
79
+ # its parent element. Note that it MAY be equal.
80
+ def valid?
81
+ # notice we skip index 0 on purpose, because it has no parent
82
+ (1..(@contents.size - 1)).all? { |e| @contents[e] >= @contents[(e - 1) / 2] }
83
+ end
84
+
85
+ private
86
+
87
+ # Left here for reference, but unused.
88
+ # def swap(i, j)
89
+ # @contents[i], @contents[j] = @contents[j], @contents[i]
90
+ # end
91
+
92
+ def bubble_up(index)
93
+ parent_index = (index - 1) / 2 # watch out, integer division!
94
+
95
+ while index > 0 && @contents[index] < @contents[parent_index]
96
+ # if the node has a smaller value than its parent, swap these nodes
97
+ # to uphold the minheap invariant and update the index of the 'current'
98
+ # node. If the node is already at index 0, we can also stop because that
99
+ # is the root of the heap.
100
+ # swap(index, parent_index)
101
+ @contents[index], @contents[parent_index] = @contents[parent_index], @contents[index]
102
+
103
+ index = parent_index
104
+ parent_index = (index - 1) / 2 # watch out, integer division!
105
+ end
106
+ end
107
+
108
+ def bubble_down(index)
109
+ swap_value = 0
110
+ swap_index = nil
111
+
112
+ while true
113
+ left_index = (2 * index) + 1
114
+ left_value = @contents[left_index]
115
+
116
+ if left_value.nil?
117
+ # This node has no children so it can't bubble down any further.
118
+ # We're done here!
119
+ return
120
+ end
121
+
122
+ # Determine which of the child nodes has the smallest value:
123
+ right_index = left_index + 1
124
+ right_value = @contents[right_index]
125
+
126
+ if right_value.nil? or right_value > left_value
127
+ swap_value = left_value
128
+ swap_index = left_index
129
+ else
130
+ swap_value = right_value
131
+ swap_index = right_index
132
+ end
133
+
134
+ if @contents[index] < swap_value
135
+ # No need to swap, the minheap invariant is already satisfied:
136
+ return
137
+ else
138
+ # At least one of the child node has a smaller value than the current node, swap current node with that child and update current node for if it might need to bubble down even further:
139
+ # swap(index, swap_index)
140
+ @contents[index], @contents[swap_index] = @contents[swap_index], @contents[index]
141
+
142
+ index = swap_index
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2023, by Samuel Williams.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
5
  # Copyright, 2023, by Math Ieu.
6
6
 
7
7
  require_relative '../interrupt'
@@ -420,7 +420,7 @@ module IO::Event
420
420
  duration = 0 unless @ready.empty?
421
421
  error = nil
422
422
 
423
- if duration && duration > 0.0
423
+ if duration&.>(0)
424
424
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
425
425
  else
426
426
  @idle_duration = 0.0
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative 'priority_heap'
7
+
8
+ class IO
9
+ module Event
10
+ class Timers
11
+ class Handle
12
+ def initialize(time, block)
13
+ @time = time
14
+ @block = block
15
+ end
16
+
17
+ def < other
18
+ @time < other.time
19
+ end
20
+
21
+ def > other
22
+ @time > other.time
23
+ end
24
+
25
+ attr :time
26
+ attr :block
27
+
28
+ def call(...)
29
+ @block.call(...)
30
+ end
31
+
32
+ def cancel!
33
+ @block = nil
34
+ end
35
+
36
+ def cancelled?
37
+ @block.nil?
38
+ end
39
+ end
40
+
41
+ def initialize
42
+ @heap = PriorityHeap.new
43
+ @scheduled = []
44
+ end
45
+
46
+ def size
47
+ flush!
48
+
49
+ return @heap.size
50
+ end
51
+
52
+ # Schedule a block to be called at a specific time in the future.
53
+ # @parameter time [Float] The time at which the block should be called, relative to {#now}.
54
+ def schedule(time, block)
55
+ handle = Handle.new(time, block)
56
+
57
+ @scheduled << handle
58
+
59
+ return handle
60
+ end
61
+
62
+ # Schedule a block to be called after a specific time offset, relative to the current time as returned by {#now}.
63
+ # @parameter offset [#to_f] The time offset from the current time at which the block should be called.
64
+ def after(offset, &block)
65
+ schedule(self.now + offset.to_f, block)
66
+ end
67
+
68
+ def wait_interval(now = self.now)
69
+ flush!
70
+
71
+ while handle = @heap.peek
72
+ if handle.cancelled?
73
+ @heap.pop
74
+ else
75
+ return handle.time - now
76
+ end
77
+ end
78
+ end
79
+
80
+ def now
81
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
82
+ end
83
+
84
+ def fire(now = self.now)
85
+ # Flush scheduled timers into the heap:
86
+ flush!
87
+
88
+ # Get the earliest timer:
89
+ while handle = @heap.peek
90
+ if handle.cancelled?
91
+ @heap.pop
92
+ elsif handle.time <= now
93
+ # Remove the earliest timer from the heap:
94
+ @heap.pop
95
+
96
+ # Call the block:
97
+ handle.call(now)
98
+ else
99
+ break
100
+ end
101
+ end
102
+ end
103
+
104
+ protected def flush!
105
+ while handle = @scheduled.pop
106
+ @heap.push(handle) unless handle.cancelled?
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -5,6 +5,6 @@
5
5
 
6
6
  class IO
7
7
  module Event
8
- VERSION = "1.5.1"
8
+ VERSION = "1.6.5"
9
9
  end
10
10
  end
data/lib/io/event.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2023, by Samuel Williams.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'event/version'
7
7
  require_relative 'event/selector'
8
+ require_relative 'event/timers'
8
9
 
9
10
  begin
10
11
  require 'IO_Event'
data/license.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # MIT License
2
2
 
3
+ Copyright, 2021, by Wander Hillen.
3
4
  Copyright, 2021-2024, by Samuel Williams.
4
5
  Copyright, 2021, by Delton Ding.
5
6
  Copyright, 2021-2024, by Benoit Daloze.
data/readme.md CHANGED
@@ -6,7 +6,7 @@ Provides low level cross-platform primitives for constructing event loops, with
6
6
 
7
7
  ## Motivation
8
8
 
9
- The initial proof-of-concept [Async](https://github.com/socketry/async) was built on [NIO4r](https://github.com/socketry/nio4r). It was perfectly acceptable and well tested in production, however being built on `libev` was a little bit limiting. I wanted to directly built my fiber scheduler into the fabric of the event loop, which is what this gem exposes - it is specifically implemented to support building event loops beneath the fiber scheduler interface, providing an efficient C implementation of all the core operations.
9
+ The initial proof-of-concept [Async](https://github.com/socketry/async) was built on [NIO4r](https://github.com/socketry/nio4r). It was perfectly acceptable and well tested in production, however being built on `libev` was a little bit limiting. I wanted to directly build my fiber scheduler into the fabric of the event loop, which is what this gem exposes - it is specifically implemented to support building event loops beneath the fiber scheduler interface, providing an efficient C implementation of all the core operations.
10
10
 
11
11
  ## Usage
12
12
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io-event
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Math Ieu
9
+ - Wander Hillen
9
10
  - Benoit Daloze
10
11
  - Bruno Sutic
11
12
  - Alex Matchneer
@@ -43,7 +44,7 @@ cert_chain:
43
44
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
44
45
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
45
46
  -----END CERTIFICATE-----
46
- date: 2024-03-04 00:00:00.000000000 Z
47
+ date: 2024-06-23 00:00:00.000000000 Z
47
48
  dependencies: []
48
49
  description:
49
50
  email:
@@ -72,10 +73,12 @@ files:
72
73
  - lib/io/event.rb
73
74
  - lib/io/event/debug/selector.rb
74
75
  - lib/io/event/interrupt.rb
76
+ - lib/io/event/priority_heap.rb
75
77
  - lib/io/event/selector.rb
76
78
  - lib/io/event/selector/nonblock.rb
77
79
  - lib/io/event/selector/select.rb
78
80
  - lib/io/event/support.rb
81
+ - lib/io/event/timers.rb
79
82
  - lib/io/event/version.rb
80
83
  - license.md
81
84
  - readme.md
@@ -98,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
101
  - !ruby/object:Gem::Version
99
102
  version: '0'
100
103
  requirements: []
101
- rubygems_version: 3.5.3
104
+ rubygems_version: 3.5.11
102
105
  signing_key:
103
106
  specification_version: 4
104
107
  summary: An event loop.
metadata.gz.sig CHANGED
Binary file