libvirt_async 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2f42eb9b4f528ea66469c50b66465d45b3eaca161c4d6eb82e61c86861a3e884
4
+ data.tar.gz: 2b6d5b0d8522f4d78e2625841bb00b6587508e370d9baa129abc35811178be15
5
+ SHA512:
6
+ metadata.gz: 366ca6361b847d440c586d0d6716531f1cc8d295ac7b713cc314b8f2c764993ddf611242848f0d8418156141718bd9a3c7eb20a87f45f4aa092c787d90559f61
7
+ data.tar.gz: 9d6e5fce3c53248c4248c6097bd2be489505d811f035665c88a81063c27c9041419323015c7f113f8c3226677ee550f7663591aab8aa2925bb258a4602ee1d08
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /Gemfile.lock
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.3.8
6
+ before_install: gem install bundler -v 2.1.0
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # Changelog
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at senid231@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in libvirt_async.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~> 12.0'
7
+ gem 'minitest', '~> 5.0'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Denis Talakevich
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # LibvirtAsync
2
+
3
+ Libvirt event async implementation.
4
+ Libvirt event api implementation on Fibers based on [libvirt-ruby](https://github.com/qwe/libvirt-ruby) and [async](https://github.com/socketry/async)
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'libvirt_async'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle install
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install libvirt_async
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ require 'libvirt_async'
26
+
27
+ LibvirtAsync.use_logger!
28
+ LibvirtAsync.logger.level = Logger::Severity::DEBUG # optional for debugging
29
+ LibvirtAsync.register_implementations!
30
+ ```
31
+
32
+ ## Development
33
+
34
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
35
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
36
+
37
+ To install this gem onto your local machine, run `bundle exec rake install`.
38
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
39
+
40
+ ## Contributing
41
+
42
+ Bug reports and pull requests are welcome on GitHub at https://github.com/senid231/libvirt_async.
43
+ This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/libvirt_async/blob/master/CODE_OF_CONDUCT.md).
44
+
45
+
46
+ ## License
47
+
48
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
49
+
50
+ ## Code of Conduct
51
+
52
+ Everyone interacting in the LibvirtAsync project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/libvirt_async/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.libs << 'lib'
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'libvirt_async'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,4 @@
1
+ module LibvirtAsync
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,193 @@
1
+ module LibvirtAsync
2
+ class Handle
3
+ # Represents an event handle (usually a file descriptor). When an event
4
+ # happens to the handle, we dispatch the event to libvirt via
5
+ # Libvirt::event_invoke_handle_callback (feeding it the handle_id we returned
6
+ # from add_handle, the file descriptor, the new events, and the opaque
7
+ # data that libvirt gave us earlier).
8
+
9
+ class Monitor < Async::Wrapper
10
+ def close
11
+ cancel_monitor
12
+ end
13
+
14
+ def readiness
15
+ monitor&.readiness
16
+ end
17
+
18
+ def to_s
19
+ "#<#{self.class}:0x#{object_id.to_s(16)} readable=#{@readable&.object_id&.to_s(16)} writable=#{@writable&.object_id&.to_s(16)} alive=#{@monitor && !@monitor.closed?}>"
20
+ end
21
+
22
+ def inspect
23
+ to_s
24
+ end
25
+ end
26
+
27
+ include WithDbg
28
+
29
+ attr_reader :handle_id, :fd, :opaque, :monitor
30
+ attr_accessor :events
31
+
32
+ def initialize(handle_id, fd, events, opaque)
33
+ dbg { "#{self.class}#initialize handle_id=#{handle_id}, fd=#{fd}, events=#{events}" }
34
+
35
+ @handle_id = handle_id
36
+ @fd = fd
37
+ @events = events
38
+ @opaque = opaque
39
+ @monitor = nil
40
+ end
41
+
42
+ def register
43
+ dbg { "#{self.class}#register handle_id=#{handle_id}, fd=#{fd}" }
44
+
45
+ if (events & Libvirt::EVENT_HANDLE_ERROR) != 0
46
+ dbg { "#{self.class}#register skip EVENT_HANDLE_ERROR handle_id=#{handle_id}, fd=#{fd}" }
47
+ end
48
+ if (events & Libvirt::EVENT_HANDLE_HANGUP) != 0
49
+ dbg { "#{self.class}#register skip EVENT_HANDLE_HANGUP handle_id=#{handle_id}, fd=#{fd}" }
50
+ end
51
+
52
+ interest = events_to_interest(events)
53
+ dbg { "#{self.class}#register parse handle_id=#{handle_id}, fd=#{fd}, events=#{events}, interest=#{interest}" }
54
+
55
+ if interest.nil?
56
+ dbg { "#{self.class}#register no interest handle_id=#{handle_id}, fd=#{fd}" }
57
+ return
58
+ end
59
+
60
+ task = Util.create_task do
61
+ dbg { "#{self.class}#register_handle Async start handle_id=#{handle_id}, fd=#{fd}" }
62
+ io_mode = interest_to_io_mode(interest)
63
+
64
+ io = IO.new(fd, io_mode, autoclose: false)
65
+ @monitor = Monitor.new(io)
66
+
67
+ while @monitor.readiness == nil
68
+ cancelled = wait_io(interest)
69
+
70
+ if cancelled
71
+ dbg { "#{self.class}#register_handle async cancel handle_id=#{handle_id}, fd=#{fd}" }
72
+ break
73
+ end
74
+
75
+ dbg { "#{self.class}#register_handle async resumes readiness=#{@monitor.readiness}, handle_id=#{handle_id}, fd=#{fd}" }
76
+ events = readiness_to_events(@monitor.readiness)
77
+
78
+ unless events.nil?
79
+ dispatch(events)
80
+ break
81
+ end
82
+
83
+ dbg { "#{self.class}#register_handle async not ready readiness=#{@monitor.readiness}, handle_id=#{handle_id}, fd=#{fd}" }
84
+ end
85
+
86
+ end
87
+
88
+ dbg { "#{self.class}#register_handle invokes fiber=0x#{task.fiber.object_id.to_s(16)} handle_id=#{handle_id}, fd=#{fd}" }
89
+ task.run
90
+ dbg { "#{self.class}#register_handle ends handle_id=#{handle_id}, fd=#{fd}" }
91
+ end
92
+
93
+ def unregister
94
+ dbg { "#{self.class}#unregister handle_id=#{handle_id}, fd=#{fd}" }
95
+
96
+ if @monitor.nil?
97
+ dbg { "#{self.class}#unregister already unregistered handle_id=#{handle_id}, fd=#{fd}" }
98
+ return
99
+ end
100
+
101
+ @monitor.close
102
+ @monitor = nil
103
+ end
104
+
105
+ def to_s
106
+ "#<#{self.class}:0x#{object_id.to_s(16)} handle_id=#{handle_id} fd=#{fd} events=#{events} monitor=#{monitor}>"
107
+ end
108
+
109
+ def inspect
110
+ to_s
111
+ end
112
+
113
+ private
114
+
115
+ def dispatch(events)
116
+ dbg { "#{self.class}#dispatch starts handle_id=#{handle_id}, events=#{events}, fd=#{fd}" }
117
+
118
+ task = Util.create_task do
119
+ dbg { "#{self.class}#dispatch async starts handle_id=#{handle_id} events=#{events}, fd=#{fd}" }
120
+ Libvirt::event_invoke_handle_callback(handle_id, fd, events, opaque)
121
+ dbg { "#{self.class}#dispatch async ends handle_id=#{handle_id} received_events=#{events}, fd=#{fd}" }
122
+ end
123
+ dbg { "#{self.class}#dispatch invokes fiber=0x#{task.fiber.object_id.to_s(16)} handle_id=#{handle_id}, events=#{events}, fd=#{fd}" }
124
+ task.run
125
+
126
+ dbg { "#{self.class}#dispatch ends handle_id=#{handle_id}, events=#{events}, fd=#{fd}" }
127
+ end
128
+
129
+ def wait_io(interest)
130
+ meth = interest_to_monitor_method(interest)
131
+ begin
132
+ @monitor.public_send(meth)
133
+ false
134
+ rescue Monitor::Cancelled => e
135
+ dbg { "#{self.class}#wait_io cancelled #{e.class} #{e.message}" }
136
+ true
137
+ end
138
+ end
139
+
140
+ def interest_to_monitor_method(interest)
141
+ case interest
142
+ when :r
143
+ :wait_readable
144
+ when :w
145
+ :wait_writable
146
+ when :rw
147
+ :wait_any
148
+ else
149
+ raise ArgumentError, "invalid interest #{interest}"
150
+ end
151
+ end
152
+
153
+ def events_to_interest(events)
154
+ readable = (events & Libvirt::EVENT_HANDLE_READABLE) != 0
155
+ writable = (events & Libvirt::EVENT_HANDLE_WRITABLE) != 0
156
+ if readable && writable
157
+ :rw
158
+ elsif readable
159
+ :r
160
+ elsif writable
161
+ :w
162
+ else
163
+ nil
164
+ end
165
+ end
166
+
167
+ def interest_to_io_mode(interest)
168
+ case interest
169
+ when :rw
170
+ 'a+'
171
+ when :r
172
+ 'r'
173
+ when :w
174
+ 'w'
175
+ else
176
+ raise ArgumentError, "invalid interest #{interest}"
177
+ end
178
+ end
179
+
180
+ def readiness_to_events(readiness)
181
+ case readiness&.to_sym
182
+ when :rw
183
+ Libvirt::EVENT_HANDLE_READABLE | Libvirt::EVENT_HANDLE_WRITABLE
184
+ when :r
185
+ Libvirt::EVENT_HANDLE_READABLE
186
+ when :w
187
+ Libvirt::EVENT_HANDLE_WRITABLE
188
+ else
189
+ nil
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,180 @@
1
+ require 'async'
2
+ require 'libvirt_async/with_dbg'
3
+ require 'libvirt_async/util'
4
+ require 'libvirt_async/handle'
5
+ require 'libvirt_async/timer'
6
+
7
+ module LibvirtAsync
8
+ class Implementations
9
+ include WithDbg
10
+
11
+ def initialize
12
+ dbg { "#{self.class}#initialize" }
13
+
14
+ default_variables
15
+ end
16
+
17
+ def start
18
+ dbg { "#{self.class}#start" }
19
+
20
+ register_implementations
21
+ end
22
+
23
+ def stop
24
+ dbg { "#{self.class}#stop" }
25
+
26
+ @handles.each(&:unregister)
27
+ @timers.each(&:unregister)
28
+
29
+ default_variables
30
+ end
31
+
32
+ def print_debug_info
33
+ str = [
34
+ "#{self.class}:0x#{object_id.to_s(16)}",
35
+ "handles = [",
36
+ @handles.map(&:to_s).join("\n"),
37
+ "]",
38
+ "timers = [",
39
+ @timers.map(&:to_s).join("\n"),
40
+ "]"
41
+ ].join("\n")
42
+ LibvirtAsync.logger&.debug { str }
43
+ end
44
+
45
+ def to_s
46
+ "#<#{self.class}:0x#{object_id.to_s(16)} handles=#{@handles} timers=#{@timers}>"
47
+ end
48
+
49
+ def inspect
50
+ to_s
51
+ end
52
+
53
+ private
54
+
55
+ def default_variables
56
+ @next_handle_id = 1
57
+ @next_timer_id = 1
58
+ @handles = []
59
+ @timers = []
60
+ end
61
+
62
+ def register_implementations
63
+ dbg { "#{self.class}#register_implementations" }
64
+
65
+ Libvirt::event_register_impl(
66
+ method(:add_handle).to_proc,
67
+ method(:update_handle).to_proc,
68
+ method(:remove_handle).to_proc,
69
+ method(:add_timer).to_proc,
70
+ method(:update_timer).to_proc,
71
+ method(:remove_timer).to_proc
72
+ )
73
+ end
74
+
75
+ def add_handle(fd, events, opaque)
76
+ # add a handle to be tracked by this object. The application is
77
+ # expected to maintain a list of internal handle IDs (integers); this
78
+ # callback *must* return the current handle_id. This handle_id is used
79
+ # both by libvirt to identify the handle (during an update or remove
80
+ # callback), and is also passed by the application into libvirt when
81
+ # dispatching an event. The application *must* also store the opaque
82
+ # data given by libvirt, and return it back to libvirt later
83
+ # (see remove_handle)
84
+ dbg { "#{self.class}#add_handle starts fd=#{fd}, events=#{events}" }
85
+
86
+ @next_handle_id += 1
87
+ handle_id = @next_handle_id
88
+ handle = LibvirtAsync::Handle.new(handle_id, fd, events, opaque)
89
+ @handles << handle
90
+ handle.register
91
+
92
+ dbg { "#{self.class}#add_handle ends fd=#{fd}, events=#{events}" }
93
+ handle_id
94
+ end
95
+
96
+ def update_handle(handle_id, events)
97
+ # update a previously registered handle. Libvirt tells us the handle_id
98
+ # (which was returned to libvirt via add_handle), and the new events. It
99
+ # is our responsibility to find the correct handle and update the events
100
+ # it cares about
101
+ dbg { "#{self.class}#update_handle starts handle_id=#{handle_id}, events=#{events}" }
102
+
103
+ handle = @handles.detect { |h| h.handle_id == handle_id }
104
+ handle.events = events
105
+ handle.unregister
106
+ handle.register
107
+
108
+ dbg { "#{self.class}#update_handle ends handle_id=#{handle_id}, events=#{events}" }
109
+ nil
110
+ end
111
+
112
+ def remove_handle(handle_id)
113
+ # remove a previously registered handle. Libvirt tells us the handle_id
114
+ # (which was returned to libvirt via add_handle), and it is our
115
+ # responsibility to "forget" the handle. We must return the opaque data
116
+ # that libvirt handed us in "add_handle", otherwise we will leak memory
117
+ dbg { "#{self.class}#remove_handle starts handle_id=#{handle_id}" }
118
+
119
+ idx = @handles.index { |h| h.handle_id == handle_id }
120
+ handle = @handles.delete_at(idx)
121
+ handle.unregister
122
+
123
+ dbg { "#{self.class}#remove_handle starts handle_id=#{handle_id}" }
124
+ handle.opaque
125
+ end
126
+
127
+ def add_timer(interval, opaque)
128
+ # add a timeout to be tracked by this object. The application is
129
+ # expected to maintain a list of internal timer IDs (integers); this
130
+ # callback *must* return the current timer_id. This timer_id is used
131
+ # both by libvirt to identify the timeout (during an update or remove
132
+ # callback), and is also passed by the application into libvirt when
133
+ # dispatching an event. The application *must* also store the opaque
134
+ # data given by libvirt, and return it back to libvirt later
135
+ # (see remove_timer)
136
+ dbg { "#{self.class}#add_timer starts interval=#{interval}" }
137
+
138
+ @next_timer_id += 1
139
+ timer_id = @next_timer_id
140
+ timer = LibvirtAsync::Timer.new(timer_id, interval, opaque)
141
+ @timers << timer
142
+ timer.register
143
+
144
+ dbg { "#{self.class}#add_timer ends interval=#{interval}" }
145
+ timer_id
146
+ end
147
+
148
+ def update_timer(timer_id, interval)
149
+ # update a previously registered timer. Libvirt tells us the timer_id
150
+ # (which was returned to libvirt via add_timer), and the new interval. It
151
+ # is our responsibility to find the correct timer and update the timers
152
+ # it cares about
153
+ dbg { "#{self.class}#update_timer starts timer_id=#{timer_id}, interval=#{interval}" }
154
+
155
+ timer = @timers.detect { |t| t.timer_id == timer_id }
156
+ dbg { "#{self.class}#update_timer updating timer_id=#{timer.timer_id}" }
157
+ timer.interval = interval
158
+ timer.unregister
159
+ timer.register
160
+
161
+ dbg { "#{self.class}#update_timer ends timer_id=#{timer_id}, interval=#{interval}" }
162
+ nil
163
+ end
164
+
165
+ def remove_timer(timer_id)
166
+ # remove a previously registered timeout. Libvirt tells us the timer_id
167
+ # (which was returned to libvirt via add_timer), and it is our
168
+ # responsibility to "forget" the timer. We must return the opaque data
169
+ # that libvirt handed us in "add_timer", otherwise we will leak memory
170
+ dbg { "#{self.class}#remove_timer starts timer_id=#{timer_id}" }
171
+
172
+ idx = @timers.index { |t| t.timer_id == timer_id }
173
+ timer = @timers.delete_at(idx)
174
+ timer.unregister
175
+
176
+ dbg { "#{self.class}#remove_timer ends timer_id=#{timer_id}" }
177
+ timer.opaque
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,40 @@
1
+ module LibvirtAsync
2
+ class LogFormatter
3
+ LOG_FORMAT = "%s, %s [%d/%s/%s] %s\n".freeze
4
+ DEFAULT_DATETIME_FORMAT = "%F %T.%N".freeze
5
+
6
+ attr_accessor :datetime_format
7
+
8
+ def initialize
9
+ @datetime_format = nil
10
+ end
11
+
12
+ def call(severity, time, progname, message)
13
+ LOG_FORMAT % [
14
+ severity[0..0],
15
+ format_datetime(time),
16
+ Process.pid,
17
+ "0x#{Fiber.current.object_id.to_s(16)}",
18
+ progname,
19
+ format_message(message)
20
+ ]
21
+ end
22
+
23
+ private
24
+
25
+ def format_datetime(time)
26
+ time.strftime(@datetime_format || DEFAULT_DATETIME_FORMAT)
27
+ end
28
+
29
+ def format_message(message)
30
+ case message
31
+ when ::String
32
+ message
33
+ when ::Exception
34
+ "<#{message.class}>:#{message.message}\n#{(message.backtrace || []).join("\n")}"
35
+ else
36
+ message.inspect
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,138 @@
1
+ module LibvirtAsync
2
+ class Timer
3
+ # Represents a When a timer expires, we dispatch the event to
4
+ # libvirt via Libvirt::event_invoke_timeout_callback (feeding it the timer_id
5
+ # we returned from add_timer and the opaque data that libvirt gave us
6
+ # earlier).
7
+
8
+ class Monitor
9
+ class Cancelled < StandardError
10
+ def initialize
11
+ super('was cancelled')
12
+ end
13
+ end
14
+
15
+ attr_reader :fiber
16
+
17
+ def initialize
18
+ @fiber = nil
19
+ end
20
+
21
+ def wait(timeout)
22
+ @fiber = Async::Task.current.fiber
23
+ Async::Task.current.sleep(timeout)
24
+ @fiber = nil
25
+ end
26
+
27
+ def close
28
+ @fiber.resume(Cancelled.new) if @fiber&.alive?
29
+ @fiber = nil
30
+ end
31
+
32
+ def to_s
33
+ "#<#{self.class}:0x#{object_id.to_s(16)} fiber=#{@fiber.&object_id&.to_s(16)} alive=#{@fiber&.alive?}>"
34
+ end
35
+
36
+ def inspect
37
+ to_s
38
+ end
39
+ end
40
+
41
+ include WithDbg
42
+
43
+ attr_reader :timer_id, :opaque, :monitor
44
+ attr_accessor :last_fired, :interval
45
+
46
+ def initialize(timer_id, interval, opaque)
47
+ dbg { "#{self.class}#initialize timer_id=#{timer_id}, interval=#{interval}" }
48
+
49
+ @timer_id = timer_id
50
+ @interval = interval.to_f / 1000.to_f
51
+ @opaque = opaque
52
+ @last_fired = Time.now.to_f
53
+ @monitor = nil
54
+ end
55
+
56
+ def wait_time
57
+ return if interval < 0
58
+ last_fired + interval
59
+ end
60
+
61
+ def register
62
+ dbg { "#{self.class}#register starts timer_id=#{timer_id}, interval=#{interval}" }
63
+
64
+ if wait_time.nil?
65
+ dbg { "#{self.class}#register no wait time timer_id=#{timer_id}, interval=#{interval}" }
66
+ return
67
+ end
68
+
69
+ task = Util.create_task do
70
+ dbg { "#{self.class}#register async starts timer_id=#{timer_id}, interval=#{interval}" }
71
+ now_time = Time.now.to_f
72
+ timeout = wait_time > now_time ? wait_time - now_time : 0
73
+ @monitor = Monitor.new
74
+ cancelled = wait_timer(timeout)
75
+
76
+ if cancelled
77
+ dbg { "#{self.class}#register async cancel timer_id=#{timer_id}, interval=#{interval}" }
78
+ else
79
+ dbg { "#{self.class}#register async ready timer_id=#{timer_id}, interval=#{interval}" }
80
+ self.last_fired = Time.now.to_f
81
+ dispatch
82
+ end
83
+ end
84
+
85
+ dbg { "#{self.class}#register invokes fiber=0x#{task.fiber.object_id.to_s(16)} timer_id=#{timer_id}, interval=#{interval}" }
86
+ task.run
87
+ dbg { "#{self.class}#register ends timer_id=#{timer_id}, interval=#{interval}" }
88
+ end
89
+
90
+ def unregister
91
+ dbg { "#{self.class}#unregister_timer timer_id=#{timer_id}, interval=#{interval}" }
92
+
93
+ if @monitor.nil?
94
+ dbg { "#{self.class}#unregister_timer already unregistered timer_id=#{timer_id}, interval=#{interval}" }
95
+ return
96
+ end
97
+
98
+ @monitor.close
99
+ @monitor = nil
100
+ end
101
+
102
+ def to_s
103
+ "#<#{self.class}:0x#{object_id.to_s(16)} timer_id=#{timer_id} interval=#{interval} last_fired=#{last_fired} monitor=#{monitor}>"
104
+ end
105
+
106
+ def inspect
107
+ to_s
108
+ end
109
+
110
+ private
111
+
112
+ def dispatch
113
+ dbg { "#{self.class}#dispatch starts timer_id=#{timer_id}, interval=#{interval}" }
114
+
115
+ task = Util.create_task do
116
+ dbg { "#{self.class}#dispatch async starts timer_id=#{timer_id}, interval=#{interval}" }
117
+ Libvirt::event_invoke_timeout_callback(timer_id, opaque)
118
+ dbg { "#{self.class}#dispatch async async ends timer_id=#{timer_id}, interval=#{interval}" }
119
+ end
120
+
121
+ dbg { "#{self.class}#dispatch invokes fiber=0x#{task.fiber.object_id.to_s(16)} timer_id=#{timer_id}, interval=#{interval}" }
122
+ task.run
123
+
124
+ dbg { "#{self.class}#dispatch ends timer_id=#{timer_id}, interval=#{interval}" }
125
+ end
126
+
127
+ def wait_timer(timeout)
128
+ begin
129
+ @monitor.wait(timeout)
130
+ false
131
+ rescue Monitor::Cancelled => e
132
+ dbg { "#{self.class}#wait_timer cancelled #{e.class} #{e.message}" }
133
+ true
134
+ end
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,11 @@
1
+ module LibvirtAsync
2
+ module Util
3
+
4
+ def create_task(&block)
5
+ Async::Task.new(Async::Task.current.reactor, &block)
6
+ end
7
+
8
+ module_function :create_task
9
+
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module LibvirtAsync
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,19 @@
1
+ require 'active_support/concern'
2
+
3
+ module LibvirtAsync
4
+ module WithDbg
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def dbg(progname = nil, &block)
9
+ LibvirtAsync.logger&.debug(progname || "0x#{object_id.to_s(16)}", &block)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def dbg(progname = nil, &block)
16
+ LibvirtAsync.logger&.debug(progname || "0x#{object_id.to_s(16)}", &block)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,77 @@
1
+ require 'libvirt_async/version'
2
+ require 'libvirt_async/error'
3
+ require 'libvirt_async/log_formatter'
4
+ require 'libvirt_async/implementations'
5
+
6
+ module LibvirtAsync
7
+ def register_implementations!
8
+ return false unless @implementations.nil?
9
+
10
+ @implementations = Implementations.new
11
+ @implementations.start
12
+ true
13
+ end
14
+
15
+ module_function :register_implementations!
16
+
17
+ def unregister_implementations!
18
+ return false if @implementations.nil?
19
+ @implementations.stop
20
+ @implementations = nil
21
+ true
22
+ end
23
+
24
+ module_function :unregister_implementations!
25
+
26
+ def logger=(value)
27
+ @logger = value
28
+ end
29
+
30
+ module_function :logger=
31
+
32
+ def logger
33
+ @logger
34
+ end
35
+
36
+ module_function :logger
37
+
38
+ def build_logger(io, formatter: LogFormatter.new, progname: nil, level: :info, datetime_format: nil)
39
+ formatter&.datetime_format = datetime_format unless datetime_format.nil?
40
+ logger = Logger.new(io, formatter: formatter, progname: progname, level: level)
41
+ logger
42
+ end
43
+
44
+ module_function :build_logger
45
+
46
+ def use_logger!(io = STDOUT, options = {})
47
+ self.logger = build_logger(io, options)
48
+ end
49
+
50
+ module_function :use_logger!
51
+
52
+ def start_debug_logging!(timeout = 2)
53
+ LibvirtAsync.logger.debug { "scheduling debug logging!" }
54
+ @debug_task = Util.create_task do
55
+ LibvirtAsync.logger.debug { "starting debug logging!" }
56
+ begin
57
+ while true do
58
+ raise Error, 'implementations not registered' if @implementations.nil?
59
+ @implementations.print_debug_info
60
+ Async::Task.current.reactor.sleep timeout
61
+ end
62
+ rescue Error => e
63
+ LibvirtAsync.logger.debug { "stopping debug logging! #{e.message}" }
64
+ end
65
+ end
66
+ Async::Task.current.reactor << @debug_task.fiber
67
+ end
68
+
69
+ module_function :start_debug_logging!
70
+
71
+ def stop_debug_logging!
72
+ @debug_task&.stop(true)
73
+ @debug_task = nil
74
+ end
75
+
76
+ module_function :stop_debug_logging!
77
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'lib/libvirt_async/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'libvirt_async'
5
+ spec.version = LibvirtAsync::VERSION
6
+ spec.authors = ['Denis Talakevich']
7
+ spec.email = ['senid231@gmail.com']
8
+
9
+ spec.summary = 'Libvirt event async implementation.'
10
+ spec.description = 'Libvirt event implementation on Fibers based on libvirt-ruby and async gems.'
11
+ spec.homepage = 'https://github.com/senid231/libvirt_async'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/CHANGELOG.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_dependency 'ruby-libvirt', '~> 0.7'
29
+ spec.add_dependency 'async', '~> 1.24'
30
+ spec.add_dependency 'activesupport'
31
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libvirt_async
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Denis Talakevich
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ruby-libvirt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.24'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.24'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Libvirt event implementation on Fibers based on libvirt-ruby and async
56
+ gems.
57
+ email:
58
+ - senid231@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".travis.yml"
65
+ - CHANGELOG.md
66
+ - CODE_OF_CONDUCT.md
67
+ - Gemfile
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - bin/console
72
+ - bin/setup
73
+ - lib/libvirt_async.rb
74
+ - lib/libvirt_async/error.rb
75
+ - lib/libvirt_async/handle.rb
76
+ - lib/libvirt_async/implementations.rb
77
+ - lib/libvirt_async/log_formatter.rb
78
+ - lib/libvirt_async/timer.rb
79
+ - lib/libvirt_async/util.rb
80
+ - lib/libvirt_async/version.rb
81
+ - lib/libvirt_async/with_dbg.rb
82
+ - libvirt_async.gemspec
83
+ homepage: https://github.com/senid231/libvirt_async
84
+ licenses:
85
+ - MIT
86
+ metadata:
87
+ homepage_uri: https://github.com/senid231/libvirt_async
88
+ source_code_uri: https://github.com/senid231/libvirt_async
89
+ changelog_uri: https://github.com/senid231/libvirt_async/CHANGELOG.md
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 2.3.0
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubygems_version: 3.0.6
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Libvirt event async implementation.
109
+ test_files: []