io-event 1.5.1 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
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