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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/ext/extconf.rb +2 -0
- data/ext/io/event/selector/epoll.c +9 -11
- data/ext/io/event/selector/kqueue.c +9 -12
- data/ext/io/event/selector/selector.h +3 -2
- data/ext/io/event/selector/uring.c +8 -11
- data/lib/io/event/priority_heap.rb +148 -0
- data/lib/io/event/selector/select.rb +2 -2
- data/lib/io/event/timers.rb +111 -0
- data/lib/io/event/version.rb +1 -1
- data/lib/io/event.rb +2 -1
- data/license.md +1 -0
- data/readme.md +1 -1
- data.tar.gz.sig +0 -0
- metadata +6 -3
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53b1f32affa83ac713eaaf8fba4b9a1c9d2f25421908759dde9b5adec11c498f
|
4
|
+
data.tar.gz: 6504222eab182d55f2b56ef91c4de9f301f509a69fff17f286c03f0ef0315786
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac5f863272fb2a532d1adbe16e2960f78dd6f4ce52891e391f301d40bb28ad74e2775348d55610f1f308f7a69b0b362e93f794b44fe94b4ec6e91331a2e851d5
|
7
|
+
data.tar.gz: 042a1a3ba4ec1a3e438d823b9d9348eeebc86be2464030744a9a01ff50654aeeb229fb79729083d3f1bdb2feb968dcee8ec320d33de7083196eb1afd887c5ef6
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/ext/extconf.rb
CHANGED
@@ -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 (
|
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
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
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
|
-
|
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
|
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 (
|
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
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
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
|
-
|
816
|
+
return storage;
|
820
817
|
}
|
821
818
|
|
822
819
|
static
|
@@ -901,24 +901,21 @@ struct __kernel_timespec * make_timeout(VALUE duration, struct __kernel_timespec
|
|
901
901
|
return NULL;
|
902
902
|
}
|
903
903
|
|
904
|
-
if (
|
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
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
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
|
-
|
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-
|
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
|
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
|
data/lib/io/event/version.rb
CHANGED
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-
|
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
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
|
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
|
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-
|
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.
|
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
|