fiber_scheduler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fd5761664b48ce8937367c38ae5424c2aad1ffcbac56fac2c002de020f08b6fc
4
+ data.tar.gz: 426a54cbbd96a1b5eb54794b2a6b295be2af98413cbfb7978780ca57ff8347f7
5
+ SHA512:
6
+ metadata.gz: 9cdf31791771ecd2fec6fee05a7849954e71a2d6596ea429ec99300a558bd8c129a8e402e7bcae6c1c093765ac9738b4944e70de26a423ae62c440f4c9d10e18
7
+ data.tar.gz: 4cf8d6bd02b7a51ef03e902da0bb25bbd5fae16ebf82c1b5aa7cf68d84b723b458be1b9106258fff527e17704ffba3850a0b377b735bf21c0638b62c7930d20d
@@ -0,0 +1 @@
1
+ require_relative "../fiber_scheduler"
@@ -0,0 +1,40 @@
1
+ class FiberScheduler
2
+ class Trigger
3
+ include Comparable
4
+
5
+ attr_reader :time
6
+
7
+ def initialize(duration, &block)
8
+ @time = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
9
+ @block = block
10
+
11
+ @disabled = nil
12
+ end
13
+
14
+ def <=>(other)
15
+ raise unless other.is_a?(self.class)
16
+
17
+ @time <=> other.time
18
+ end
19
+
20
+ def call
21
+ @block.call
22
+ end
23
+
24
+ def interval
25
+ @time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
26
+ end
27
+
28
+ def disable
29
+ @disabled = true
30
+ end
31
+
32
+ def disabled?
33
+ @disabled
34
+ end
35
+
36
+ def inspect
37
+ "#<#{self.class} time=#{@time}>"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ require_relative "trigger"
2
+
3
+ class FiberScheduler
4
+ class Triggers
5
+ def initialize
6
+ # Array is sorted by Trigger#time
7
+ @triggers = []
8
+ end
9
+
10
+ def call
11
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
+
13
+ while @triggers.any? && @triggers.first.time <= now
14
+ trigger = @triggers.shift
15
+ unless trigger.disabled?
16
+ trigger.call
17
+ end
18
+ end
19
+ end
20
+
21
+ def add(duration, &block)
22
+ trigger = Trigger.new(duration, &block)
23
+
24
+ if @triggers.empty?
25
+ @triggers << trigger
26
+ return trigger
27
+ end
28
+
29
+ # binary search
30
+ min = 0
31
+ max = @triggers.size - 1
32
+ while min <= max
33
+ index = (min + max) / 2
34
+ t = @triggers[index]
35
+
36
+ if t > trigger
37
+ if index.zero? || @triggers[index - 1] <= trigger
38
+ # found it
39
+ break
40
+ else
41
+ # @triggers[index - 1] > trigger
42
+ max = index - 1
43
+ end
44
+ else
45
+ # t <= trigger
46
+ index += 1
47
+ min = index
48
+ end
49
+ end
50
+
51
+ @triggers.insert(index, trigger)
52
+ trigger
53
+ end
54
+
55
+ def interval
56
+ # Prune disabled triggers
57
+ while @triggers.first&.disabled?
58
+ @triggers.shift
59
+ end
60
+
61
+ return if @triggers.empty?
62
+
63
+ interval = @triggers.first.interval
64
+
65
+ interval >= 0 ? interval : 0
66
+ end
67
+
68
+ def inspect
69
+ @triggers.inspect
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ class FiberScheduler
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,150 @@
1
+ require "io/event"
2
+ require "resolv"
3
+ require_relative "fiber_scheduler/triggers"
4
+
5
+ module Kernel
6
+ def FiberScheduler(&block)
7
+ if Fiber.scheduler.nil?
8
+ scheduler = FiberScheduler.new
9
+ Fiber.set_scheduler(scheduler)
10
+
11
+ begin
12
+ yield
13
+ scheduler.close
14
+ ensure
15
+ Fiber.set_scheduler(nil)
16
+ end
17
+ else
18
+ # Fiber.scheduler already set, just schedule a task.
19
+ Fiber.schedule(&block)
20
+ end
21
+ end
22
+ end
23
+
24
+ class FiberScheduler
25
+ TimeoutError = Class.new(RuntimeError)
26
+ IOWaitTimeout = Class.new(TimeoutError)
27
+
28
+ def initialize
29
+ @selector = IO::Event::Selector.new(Fiber.current)
30
+ @triggers = Triggers.new
31
+
32
+ @count = 0
33
+ @nested = []
34
+ end
35
+
36
+ def run
37
+ while @count > 0
38
+ if @nested.empty?
39
+ @selector.select(@triggers.interval)
40
+ @triggers.call
41
+ else
42
+ while @nested.any?
43
+ fiber = @nested.pop
44
+ fiber.transfer
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Fiber::SchedulerInterface methods below
51
+
52
+ def close
53
+ return unless @selector
54
+
55
+ begin
56
+ run
57
+ ensure
58
+ @selector.close
59
+ @selector = nil
60
+ end
61
+ end
62
+
63
+ def block(blocker, timeout)
64
+ return @selector.transfer unless timeout
65
+
66
+ fiber = Fiber.current
67
+ trigger = @triggers.add(timeout) do
68
+ fiber.transfer if fiber.alive?
69
+ end
70
+
71
+ begin
72
+ @selector.transfer
73
+ ensure
74
+ trigger.disable
75
+ end
76
+ end
77
+
78
+ def unblock(blocker, fiber)
79
+ @selector.push(fiber)
80
+ end
81
+
82
+ def kernel_sleep(duration = nil)
83
+ return @selector.transfer unless duration
84
+
85
+ block(:sleep, duration)
86
+ end
87
+
88
+ def address_resolve(hostname)
89
+ Resolv.getaddresses(hostname)
90
+ end
91
+
92
+ def io_wait(io, events, timeout = nil)
93
+ fiber = Fiber.current
94
+ return @selector.io_wait(fiber, io, events) unless timeout
95
+
96
+ trigger = @triggers.raise_in(timeout, IOWaitTimeout)
97
+ # trigger = @triggers.add(timeout) do
98
+ # fiber.raise(IOWaitTimeout) if fiber.alive?
99
+ # end
100
+
101
+ begin
102
+ @selector.io_wait(fiber, io, events)
103
+ rescue IOWaitTimeout
104
+ false
105
+ ensure
106
+ trigger.disable
107
+ end
108
+ end
109
+
110
+ def io_read(io, buffer, length)
111
+ @selector.io_read(Fiber.current, io, buffer, length)
112
+ end
113
+
114
+ def io_write(io, buffer, length)
115
+ @selector.io_write(Fiber.current, io, buffer, length)
116
+ end
117
+
118
+ def process_wait(pid, flags)
119
+ @selector.process_wait(Fiber.current, pid, flags)
120
+ end
121
+
122
+ def timeout_after(duration, exception = TimeoutError, message = "timeout")
123
+ fiber = Fiber.current
124
+ trigger = @triggers.add(duration) do
125
+ fiber.raise(exception, message) if fiber.alive?
126
+ end
127
+
128
+ begin
129
+ yield duration
130
+ ensure
131
+ trigger.disable
132
+ end
133
+ end
134
+
135
+ def fiber(&block)
136
+ unless Fiber.blocking?
137
+ # nested Fiber.schedule
138
+ @nested << Fiber.current
139
+ end
140
+
141
+ fiber = Fiber.new(blocking: false) do
142
+ @count += 1
143
+ block.call
144
+ ensure
145
+ @count -= 1
146
+ end
147
+
148
+ fiber.tap(&:transfer)
149
+ end
150
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fiber_scheduler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bruno Sutic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: io-event
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: standard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.7'
83
+ description:
84
+ email: code@brunosutic.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - lib/fiber/scheduler.rb
90
+ - lib/fiber_scheduler.rb
91
+ - lib/fiber_scheduler/trigger.rb
92
+ - lib/fiber_scheduler/triggers.rb
93
+ - lib/fiber_scheduler/version.rb
94
+ homepage: https://github.com/bruno-/fiber_scheduler
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 3.1.0
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.3.3
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Fiber scheduler
117
+ test_files: []