cztop-reactor 0.1.0.pre20170316155217
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 +7 -0
- data/.document +5 -0
- data/.rdoc_options +16 -0
- data/.simplecov +9 -0
- data/ChangeLog +30 -0
- data/History.md +4 -0
- data/LICENSE.txt +20 -0
- data/Manifest.txt +14 -0
- data/README.md +113 -0
- data/Rakefile +100 -0
- data/lib/cztop/reactor.rb +350 -0
- data/lib/cztop/reactor/event.rb +68 -0
- data/spec/cztop/reactor/event_spec.rb +41 -0
- data/spec/cztop/reactor_spec.rb +183 -0
- data/spec/spec_helper.rb +23 -0
- metadata +250 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9ff9e9366325f56b5134fb0c8b7123c90b259709
|
4
|
+
data.tar.gz: f1effb0b47c1199772305b414d2f31e34bdb7798
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 41e02a93aab2823b3dd3b6d05e2da1a15d873f99cf1efaa4068a7b632ee595b2641d0bda7a5d13b43eb931095235000edfb93a9daadb71d7517187530ffcdda6
|
7
|
+
data.tar.gz: 3e391f785107c50fa41f4a4bb761fc1ad14cef76411cb3bb52dfb61d58a952376294a0d9aa86f0c81487984e8f5d9bfb2e0b48a35d9c2b17dbd2e5a91180a006
|
data/.document
ADDED
data/.rdoc_options
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
--- !ruby/object:RDoc::Options
|
2
|
+
encoding: UTF-8
|
3
|
+
static_path: []
|
4
|
+
rdoc_include:
|
5
|
+
- .
|
6
|
+
charset: UTF-8
|
7
|
+
exclude:
|
8
|
+
hyperlink_all: false
|
9
|
+
line_numbers: false
|
10
|
+
main_page: README.md
|
11
|
+
markup: markdown
|
12
|
+
show_hash: false
|
13
|
+
tab_width: 8
|
14
|
+
title: CZTop-Reactor Documentation
|
15
|
+
visibility: :protected
|
16
|
+
webcvs:
|
data/.simplecov
ADDED
data/ChangeLog
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
2017-03-16 Michael Granger <ged@FaerieMUD.org>
|
2
|
+
|
3
|
+
* .hgignore:
|
4
|
+
Ignore built gems
|
5
|
+
[594168b03b71] [tip]
|
6
|
+
|
7
|
+
* README.md:
|
8
|
+
Fix link in the README
|
9
|
+
[0391133d132d] [github/master]
|
10
|
+
|
11
|
+
* .hgignore:
|
12
|
+
Ignore generated docs directory
|
13
|
+
[ad4092564786]
|
14
|
+
|
15
|
+
* README.md:
|
16
|
+
Tabs -> spaces in the README
|
17
|
+
[41ec0ff65005]
|
18
|
+
|
19
|
+
* cztop-reactor.gemspec:
|
20
|
+
Add generated gemspec
|
21
|
+
[b3fcff597575]
|
22
|
+
|
23
|
+
* .document, .editorconfig, .gems, .hgignore, .hoerc, .pryrc,
|
24
|
+
.rdoc_options, .ruby-gemset, .ruby-version, .simplecov, Gemfile,
|
25
|
+
History.md, LICENSE.txt, Manifest.txt, README.md, Rakefile,
|
26
|
+
certs/ged.pem, lib/cztop/reactor.rb, lib/cztop/reactor/event.rb,
|
27
|
+
spec/cztop/reactor/event_spec.rb, spec/cztop/reactor_spec.rb,
|
28
|
+
spec/spec_helper.rb:
|
29
|
+
Initial commit.
|
30
|
+
[5e3f2358036b]
|
data/History.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2017 Michael Granger
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
.document
|
2
|
+
.rdoc_options
|
3
|
+
.simplecov
|
4
|
+
ChangeLog
|
5
|
+
History.md
|
6
|
+
LICENSE.txt
|
7
|
+
Manifest.txt
|
8
|
+
README.md
|
9
|
+
Rakefile
|
10
|
+
lib/cztop/reactor.rb
|
11
|
+
lib/cztop/reactor/event.rb
|
12
|
+
spec/cztop/reactor/event_spec.rb
|
13
|
+
spec/cztop/reactor_spec.rb
|
14
|
+
spec/spec_helper.rb
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# CZTop-Reactor
|
2
|
+
|
3
|
+
home
|
4
|
+
: http://deveiate.org/projects/CZTop-Reactor
|
5
|
+
|
6
|
+
code
|
7
|
+
: http://bitbucket.org/ged/cztop-reactor
|
8
|
+
|
9
|
+
github
|
10
|
+
: https://github.com/ged/cztop-reactor
|
11
|
+
|
12
|
+
docs
|
13
|
+
: http://deveiate.org/code/cztop-reactor
|
14
|
+
|
15
|
+
|
16
|
+
## Description
|
17
|
+
|
18
|
+
This is an implementation of the Reactor pattern described in [Pattern-Oriented
|
19
|
+
Software Architecture (Volume 2)][POSA2]. It allows an asynchronous application
|
20
|
+
to be described as one or more "reactions" to events, in this case either I/O
|
21
|
+
conditions on a ZMQ socket or a timer expiring.
|
22
|
+
|
23
|
+
A simple example:
|
24
|
+
|
25
|
+
# Start a SERVER socket, and print out any messages sent to it
|
26
|
+
reactor = CZTop::Reactor.new
|
27
|
+
socket = CZTop::Socket::SERVER.new
|
28
|
+
socket.bind( 'tcp://0.0.0.0:8' )
|
29
|
+
reactor.register( socket, :read ) do |event|
|
30
|
+
if event.readable?
|
31
|
+
message = event.socket.receive
|
32
|
+
puts "Read: %p" % [ message.to_a ]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
reactor.start_polling
|
36
|
+
|
37
|
+
|
38
|
+
## Prerequisites
|
39
|
+
|
40
|
+
It should run under any Ruby interpreter that CZTop will, which at the time of
|
41
|
+
this writing includes:
|
42
|
+
|
43
|
+
* MRI (2.3, 2.2)
|
44
|
+
* Rubinius (HEAD)
|
45
|
+
* JRuby 9000 (HEAD)
|
46
|
+
|
47
|
+
I am also using it (and CZTop) under MRI 2.4.
|
48
|
+
|
49
|
+
|
50
|
+
## Installation
|
51
|
+
|
52
|
+
$ gem install cztop-reactor
|
53
|
+
|
54
|
+
|
55
|
+
## Reasons
|
56
|
+
|
57
|
+
I considered submitting this as a patch to `cztop`, but in the end elected to
|
58
|
+
distribute it as a gem for two reasons:
|
59
|
+
|
60
|
+
1. It depends on the `timers` gem, and I didn't want to add this dependency to
|
61
|
+
`cztop`. If the [`ztimerset`][ztimerset] spec ever comes out of draft status
|
62
|
+
and `cztop` adds an implementation of it, this wouldn't be necessary.
|
63
|
+
2. I'm not confident enough in my FFI knowledge to know if this is an
|
64
|
+
appropriate way to implement this class. I've written numerous C extensions
|
65
|
+
for Ruby, but FFI is still a bit of a mystery to me, and likely will remain
|
66
|
+
so for the foreseeable future given my misgivings about using it.
|
67
|
+
|
68
|
+
|
69
|
+
## Contributing
|
70
|
+
|
71
|
+
You can check out the current development source with Mercurial via its
|
72
|
+
{project page}[http://bitbucket.org/ged/cztop-reactor]. Or if you prefer Git,
|
73
|
+
via {its Github mirror}[https://github.com/ged/cztop-reactor].
|
74
|
+
|
75
|
+
After checking out the source, run:
|
76
|
+
|
77
|
+
$ rake newb
|
78
|
+
|
79
|
+
This task will install any missing dependencies, run the tests/specs,
|
80
|
+
and generate the API documentation.
|
81
|
+
|
82
|
+
|
83
|
+
## License
|
84
|
+
|
85
|
+
This library includes source from the CZTop gem by Patrik Wenger, which is
|
86
|
+
distributed under the terms of the [ISC
|
87
|
+
License](http://opensource.org/licenses/ISC):
|
88
|
+
|
89
|
+
> Copyright (c) 2016, Patrik Wenger
|
90
|
+
>
|
91
|
+
> Permission to use, copy, modify, and/or distribute this software for
|
92
|
+
> any purpose with or without fee is hereby granted, provided that
|
93
|
+
> the above copyright notice and this permission notice appear in all
|
94
|
+
> copies.
|
95
|
+
>
|
96
|
+
> THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
97
|
+
> WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
98
|
+
> WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
99
|
+
> AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
100
|
+
> DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
101
|
+
> OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
102
|
+
> TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
103
|
+
> PERFORMANCE OF THIS SOFTWARE.
|
104
|
+
|
105
|
+
Everything else is distributed under the same license but is:
|
106
|
+
|
107
|
+
Copyright (c) 2017, Michael Granger
|
108
|
+
|
109
|
+
|
110
|
+
[POSA2]: http://www.cs.wustl.edu/~schmidt/POSA/POSA2/
|
111
|
+
[ztimerset]: http://czmq.zeromq.org/czmq4-0:ztimerset
|
112
|
+
|
113
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'hoe'
|
5
|
+
rescue LoadError
|
6
|
+
abort "This Rakefile requires hoe (gem install hoe)"
|
7
|
+
end
|
8
|
+
|
9
|
+
GEMSPEC = 'cztop-reactor.gemspec'
|
10
|
+
|
11
|
+
|
12
|
+
Hoe.plugin :mercurial
|
13
|
+
Hoe.plugin :signing
|
14
|
+
Hoe.plugin :deveiate
|
15
|
+
|
16
|
+
Hoe.plugins.delete :rubyforge
|
17
|
+
Hoe.plugins.delete :gemcutter # Remove for public gems
|
18
|
+
|
19
|
+
hoespec = Hoe.spec 'cztop-reactor' do |spec|
|
20
|
+
self.readme_file = 'README.md'
|
21
|
+
self.history_file = 'History.md'
|
22
|
+
self.extra_rdoc_files = FileList[ '*.rdoc', '*.md' ]
|
23
|
+
self.urls = {
|
24
|
+
home: 'http://deveiate.org/projects/cztop-reactor',
|
25
|
+
code: 'http://bitbucket.org/ged/cztop-reactor',
|
26
|
+
docs: 'http://deveiate.org/code/cztop-reactor',
|
27
|
+
github: 'http://github.com/ged/cztop-reactor',
|
28
|
+
}
|
29
|
+
|
30
|
+
spec.license 'ISC'
|
31
|
+
|
32
|
+
spec.developer 'Michael Granger', 'ged@FaerieMUD.org'
|
33
|
+
|
34
|
+
spec.dependency 'loggability', '~> 0.14'
|
35
|
+
spec.dependency 'cztop', '~> 0.11'
|
36
|
+
spec.dependency 'timers', '~> 4.1'
|
37
|
+
|
38
|
+
spec.dependency 'hoe-deveiate', '~> 0.9', :developer
|
39
|
+
spec.dependency 'simplecov', '~> 0.13', :developer
|
40
|
+
spec.dependency 'rdoc-generator-fivefish', '~> 0.3', :developer
|
41
|
+
|
42
|
+
spec.require_ruby_version( '>=2.2.4' )
|
43
|
+
spec.hg_sign_tags = true if spec.respond_to?( :hg_sign_tags= )
|
44
|
+
spec.check_history_on_release = true if spec.respond_to?( :check_history_on_release= )
|
45
|
+
|
46
|
+
self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
ENV['VERSION'] ||= hoespec.spec.version.to_s
|
51
|
+
|
52
|
+
# Run the tests before checking in
|
53
|
+
task 'hg:precheckin' => [ :check_history, :check_manifest, :gemspec, :spec ]
|
54
|
+
|
55
|
+
task :test => :spec
|
56
|
+
|
57
|
+
# Rebuild the ChangeLog immediately before release
|
58
|
+
task :prerelease => 'ChangeLog'
|
59
|
+
CLOBBER.include( 'ChangeLog' )
|
60
|
+
|
61
|
+
desc "Build a coverage report"
|
62
|
+
task :coverage do
|
63
|
+
ENV["COVERAGE"] = 'yes'
|
64
|
+
Rake::Task[:spec].invoke
|
65
|
+
end
|
66
|
+
CLOBBER.include( 'coverage' )
|
67
|
+
|
68
|
+
|
69
|
+
# Use the fivefish formatter for docs generated from development checkout
|
70
|
+
if File.directory?( '.hg' )
|
71
|
+
require 'rdoc/task'
|
72
|
+
|
73
|
+
Rake::Task[ 'docs' ].clear
|
74
|
+
RDoc::Task.new( 'docs' ) do |rdoc|
|
75
|
+
rdoc.markup = 'markdown'
|
76
|
+
rdoc.main = "README.md"
|
77
|
+
rdoc.rdoc_files.include( "*.md", "ChangeLog", "lib/**/*.rb" )
|
78
|
+
|
79
|
+
rdoc.generator = :fivefish
|
80
|
+
rdoc.title = 'CZTop-Reactor'
|
81
|
+
rdoc.rdoc_dir = 'doc'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
task :gemspec => GEMSPEC
|
86
|
+
file GEMSPEC => __FILE__
|
87
|
+
task GEMSPEC do |task|
|
88
|
+
spec = $hoespec.spec
|
89
|
+
spec.files.delete( '.gemtest' )
|
90
|
+
spec.signing_key = nil
|
91
|
+
spec.cert_chain = ['certs/ged.pem']
|
92
|
+
spec.version = "#{spec.version.bump}.0.pre#{Time.now.strftime("%Y%m%d%H%M%S")}"
|
93
|
+
File.open( task.name, 'w' ) do |fh|
|
94
|
+
fh.write( spec.to_ruby )
|
95
|
+
end
|
96
|
+
end
|
97
|
+
CLOBBER.include( GEMSPEC.to_s )
|
98
|
+
|
99
|
+
task :default => :gemspec
|
100
|
+
|
@@ -0,0 +1,350 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'timers'
|
6
|
+
require 'ffi'
|
7
|
+
|
8
|
+
require 'cztop'
|
9
|
+
require 'cztop/poller'
|
10
|
+
require 'cztop/has_ffi_delegate'
|
11
|
+
|
12
|
+
|
13
|
+
# An implementation of the Reactor pattern described in
|
14
|
+
# [Pattern-Oriented Software Architecture (Volume 2)][POSA2]. It allows
|
15
|
+
# an asynchronous application to be described as one or more "reactions"
|
16
|
+
# to events, in this case either I/O conditions on a ZMQ socket or a
|
17
|
+
# timer expiring.
|
18
|
+
#
|
19
|
+
# [POSA2]: http://www.cs.wustl.edu/~schmidt/POSA/POSA2/
|
20
|
+
#
|
21
|
+
class CZTop::Reactor
|
22
|
+
extend Loggability
|
23
|
+
|
24
|
+
# The version of this library
|
25
|
+
VERSION = '0.0.1'
|
26
|
+
|
27
|
+
# The maximum number of seconds to wait for events when there are no timers
|
28
|
+
# registered.
|
29
|
+
DEFAULT_POLL_INTERVAL = 0.250
|
30
|
+
|
31
|
+
# The events that can be registered and the corresponding mask
|
32
|
+
VALID_EVENTS = {
|
33
|
+
read: CZTop::Poller::ZMQ::POLLIN,
|
34
|
+
write: CZTop::Poller::ZMQ::POLLOUT,
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
|
38
|
+
autoload :Event, 'cztop/reactor/event'
|
39
|
+
|
40
|
+
|
41
|
+
# Loggability API -- set up a logger for this class
|
42
|
+
log_as :cztop
|
43
|
+
|
44
|
+
|
45
|
+
### Create a new CZTop::Reactor
|
46
|
+
def initialize
|
47
|
+
@sockets = Hash.new do |hsh,key|
|
48
|
+
hsh[ key ] = { events: [], handler: nil }
|
49
|
+
end
|
50
|
+
@timers = Timers::Group.new
|
51
|
+
@wakeup_timer = @timers.every( DEFAULT_POLL_INTERVAL ) do
|
52
|
+
# No-op -- just ensures that new timers that are registered are only
|
53
|
+
# delayed by (at most) the DEFAULT_POLL_INTERVAL before they start.
|
54
|
+
end
|
55
|
+
|
56
|
+
@socket_pointers = {}
|
57
|
+
|
58
|
+
@poller_ptr = CZTop::Poller::ZMQ.poller_new
|
59
|
+
ObjectSpace.define_finalizer( @poller_ptr, -> (obj_id) {
|
60
|
+
# $stderr.puts "Freeing the poller pointer %p" % [ @poller_ptr ]
|
61
|
+
ptr_ptr = ::FFI::MemoryPointer.new( :pointer )
|
62
|
+
ptr_ptr.write_pointer( @poller_ptr )
|
63
|
+
CZTop::Poller::ZMQ.poller_destroy( ptr_ptr )
|
64
|
+
})
|
65
|
+
@event_ptr = ::FFI::MemoryPointer.new( CZTop::Poller::ZMQ::PollerEvent )
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
######
|
70
|
+
public
|
71
|
+
######
|
72
|
+
|
73
|
+
##
|
74
|
+
# Sockets and the handlers that handle their IO
|
75
|
+
attr_reader :sockets
|
76
|
+
|
77
|
+
##
|
78
|
+
# Registered timers as a Timers::Group
|
79
|
+
attr_reader :timers
|
80
|
+
|
81
|
+
##
|
82
|
+
# The handle of the default timer that is used to ensure the polling loop
|
83
|
+
# notices new sockets and timers.
|
84
|
+
attr_reader :wakeup_timer
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# Sockets
|
89
|
+
#
|
90
|
+
|
91
|
+
### Register the specified +socket+ with the reactor for the specified +events+.
|
92
|
+
### The following events are supported:
|
93
|
+
###
|
94
|
+
### [<tt>:read</tt>]
|
95
|
+
### Data may be read from the socket without blocking.
|
96
|
+
### [<tt>:write</tt>]
|
97
|
+
### Data may be written to the socket without blocking.
|
98
|
+
###
|
99
|
+
### Registering a handle will unregister any previously registered
|
100
|
+
### event/handler+arguments pairs associated with the handle.
|
101
|
+
###
|
102
|
+
def register( socket, *events, &handler )
|
103
|
+
raise LocalJumpError, "no handler given" unless handler
|
104
|
+
|
105
|
+
self.unregister( socket )
|
106
|
+
|
107
|
+
ptr = self.ptr_for_socket( socket )
|
108
|
+
rc = CZTop::Poller::ZMQ.poller_add( @poller_ptr, ptr, nil, 0 )
|
109
|
+
self.log.debug "poller_add: rc = %p" % [ rc ]
|
110
|
+
CZTop::HasFFIDelegate.raise_zmq_err if rc == -1
|
111
|
+
|
112
|
+
self.log.info "Registered: %p with handler: %p" % [ socket, handler ]
|
113
|
+
self.sockets[ socket ][ :handler ] = handler
|
114
|
+
self.enable_events( socket, *events )
|
115
|
+
|
116
|
+
@socket_pointers[ ptr.to_i ] = socket
|
117
|
+
end
|
118
|
+
alias_method :add, :register
|
119
|
+
alias_method :register_socket, :register
|
120
|
+
|
121
|
+
|
122
|
+
### Remove the specified <tt>socket</tt> from the receiver's list of registered
|
123
|
+
### handles, if present. Returns the handle if it was registered, or
|
124
|
+
### <tt>nil</tt> if it was not.
|
125
|
+
def unregister( socket )
|
126
|
+
if self.sockets.delete( socket )
|
127
|
+
self.log.info "Unregistering: %p" % [ socket ]
|
128
|
+
ptr = self.ptr_for_socket( socket )
|
129
|
+
rc = CZTop::Poller::ZMQ.poller_remove( @poller_ptr, ptr )
|
130
|
+
self.log.debug "poller_remove: rc = %p" % [ rc ]
|
131
|
+
CZTop::HasFFIDelegate.raise_zmq_err if rc == -1
|
132
|
+
end
|
133
|
+
|
134
|
+
@socket_pointers.delete( ptr.to_i )
|
135
|
+
end
|
136
|
+
alias_method :remove, :unregister
|
137
|
+
alias_method :unregister_socket, :unregister
|
138
|
+
|
139
|
+
|
140
|
+
### Returns +true+ if the given +socket+ handle is registered with the reactor.
|
141
|
+
def registered?( socket )
|
142
|
+
return self.sockets.key?( socket )
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
### Add the specified +events+ to the list that will be polled for on the
|
147
|
+
### given +socket+.
|
148
|
+
def enable_events( socket, *events )
|
149
|
+
invalid = events - ( events & VALID_EVENTS.keys )
|
150
|
+
if !invalid.empty?
|
151
|
+
raise ArgumentError, "invalid events: %p" % [ invalid ]
|
152
|
+
end
|
153
|
+
|
154
|
+
socket = self.socket_for_ptr( socket ) if socket.is_a?( FFI::Pointer )
|
155
|
+
raise ArgumentError, "%p is not registered yet" % [ socket ] unless
|
156
|
+
self.registered?( socket )
|
157
|
+
|
158
|
+
self.sockets[ socket ][ :events ] |= events
|
159
|
+
self.update_poller_for( socket )
|
160
|
+
end
|
161
|
+
alias_method :enable_event, :enable_events
|
162
|
+
alias_method :enable_socket_events, :enable_events
|
163
|
+
alias_method :enable_socket_event, :enable_events
|
164
|
+
|
165
|
+
|
166
|
+
### Remove the specified +events+ from the list that will be polled for on
|
167
|
+
### the given +socket+ handle.
|
168
|
+
def disable_events( socket, *events )
|
169
|
+
socket = self.socket_for_ptr( socket ) if socket.is_a?( FFI::Pointer )
|
170
|
+
self.sockets[ socket ][:events] -= events
|
171
|
+
self.update_poller_for( socket )
|
172
|
+
end
|
173
|
+
alias_method :disable_socket_events, :disable_events
|
174
|
+
|
175
|
+
|
176
|
+
### Returns +true+ if the specified +event+ is enabled for the given +socket+.
|
177
|
+
def event_enabled?( socket, event )
|
178
|
+
socket = self.socket_for_ptr( socket ) if socket.is_a?( FFI::Pointer )
|
179
|
+
|
180
|
+
return false unless self.sockets.key?( socket )
|
181
|
+
return self.sockets[ socket ][ :events ].include?( event )
|
182
|
+
end
|
183
|
+
alias_method :has_event_enabled?, :event_enabled?
|
184
|
+
alias_method :socket_event_enabled?, :event_enabled?
|
185
|
+
|
186
|
+
|
187
|
+
### Returns <tt>true</tt> if no sockets are registered.
|
188
|
+
def empty?
|
189
|
+
return self.sockets.empty? && self.timers.empty?
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
### Clear all registered sockets and returns the sockets that were cleared.
|
194
|
+
def clear
|
195
|
+
sockets = self.sockets.keys
|
196
|
+
sockets.each {|sock| self.unregister(sock) }
|
197
|
+
return sockets
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
#
|
202
|
+
# Timers
|
203
|
+
#
|
204
|
+
|
205
|
+
### Register a timer that will call the specified +callback+ once after +delay+
|
206
|
+
### seconds.
|
207
|
+
def add_oneshot_timer( delay, &callback )
|
208
|
+
self.log.debug "Registering a oneshot timer: will call %p after %0.2fs" %
|
209
|
+
[ callback, delay ]
|
210
|
+
return self.timers.after( delay, &callback )
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
### Register a timer that will call the specified +callback+ once every
|
215
|
+
### +delay+ seconds until it is cancelled.
|
216
|
+
def add_periodic_timer( delay, &callback )
|
217
|
+
self.log.debug "Registering a periodic timer: will call %p every %0.2fs" %
|
218
|
+
[ callback, delay ]
|
219
|
+
return self.timers.every( delay, &callback )
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
### Remove the specified +timer+ from the reactor.
|
224
|
+
def remove_timer( timer )
|
225
|
+
timer.cancel
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
#
|
230
|
+
# Monitors
|
231
|
+
#
|
232
|
+
|
233
|
+
### Create a CZTop::Monitor for the specified +socket+ that will listen for the
|
234
|
+
### specified +events+ (which are monitor events, not I/O events). It will be automatically
|
235
|
+
### registered with the reactor for the `:read` event with the specified +callback+,
|
236
|
+
### then returned.
|
237
|
+
def register_monitor( socket, *events, &callback )
|
238
|
+
events.push( 'ALL' ) if events.empty?
|
239
|
+
|
240
|
+
monitor = CZTop::Monitor.new( socket )
|
241
|
+
monitor.listen( *events )
|
242
|
+
monitor.start
|
243
|
+
|
244
|
+
self.register( monitor.actor, :read, &callback )
|
245
|
+
|
246
|
+
return monitor
|
247
|
+
end
|
248
|
+
alias_method :start_monitor, :register_monitor
|
249
|
+
|
250
|
+
|
251
|
+
|
252
|
+
#
|
253
|
+
# Polling
|
254
|
+
#
|
255
|
+
|
256
|
+
### Poll the sockets registered to the reactor for pending events.
|
257
|
+
def start_polling
|
258
|
+
until self.empty?
|
259
|
+
self.log.debug "Polling %d sockets" % [ self.sockets.length ]
|
260
|
+
|
261
|
+
wait_interval = self.timers.wait_interval || DEFAULT_POLL_INTERVAL
|
262
|
+
|
263
|
+
# If there's a timer already due to fire, don't wait at all
|
264
|
+
event = if wait_interval > 0
|
265
|
+
self.log.debug "Waiting for IO for %fms" % [ wait_interval * 1000 ]
|
266
|
+
self.wait( wait_interval * 1000 )
|
267
|
+
else
|
268
|
+
nil
|
269
|
+
end
|
270
|
+
|
271
|
+
self.log.debug "Got event %p" % [ event ]
|
272
|
+
if event
|
273
|
+
# self.log.debug "Got event: %p" % [ event ]
|
274
|
+
handler = self.sockets[ event.socket ][ :handler ]
|
275
|
+
handler.call( event ) if handler
|
276
|
+
else
|
277
|
+
self.log.debug "Expired: firing timers."
|
278
|
+
self.timers.fire
|
279
|
+
end
|
280
|
+
|
281
|
+
self.log.debug "%d sockets after polling: %p" % [ self.sockets.length, self.sockets ]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
### Stop polling for events and prepare to shut down.
|
287
|
+
def stop_polling
|
288
|
+
self.log.debug "Stopping the poll loop."
|
289
|
+
self.clear
|
290
|
+
self.timers.cancel
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
### Return the socket object for the given +pointer+ (an FFI::Pointer), or +nil+ if the
|
295
|
+
### pointer is unknown.
|
296
|
+
def socket_for_ptr( pointer )
|
297
|
+
return @socket_pointers[ pointer.to_i ]
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
#########
|
302
|
+
protected
|
303
|
+
#########
|
304
|
+
|
305
|
+
### Waits for events on registered sockets. Returns the first such event, or +nil+ if
|
306
|
+
### no events arrived within the specified +timeout+. If +timeout+ is -1, wait
|
307
|
+
### indefinitely.
|
308
|
+
def wait( timeout=-1 )
|
309
|
+
rc = CZTop::Poller::ZMQ.poller_wait( @poller_ptr, @event_ptr, timeout )
|
310
|
+
if rc == -1
|
311
|
+
if CZMQ::FFI::Errors.errno != Errno::ETIMEDOUT::Errno
|
312
|
+
CZTop::HasFFIDelegate.raise_zmq_err
|
313
|
+
end
|
314
|
+
return nil
|
315
|
+
end
|
316
|
+
return Event.new(self, @event_ptr)
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
### Modify the underlying poller's event mask with the events +socket+ is
|
321
|
+
### interested in.
|
322
|
+
def update_poller_for( socket )
|
323
|
+
event_mask = self.mask_for( socket )
|
324
|
+
|
325
|
+
ptr = self.ptr_for_socket( socket )
|
326
|
+
rc = CZTop::Poller::ZMQ.poller_modify( @poller_ptr, ptr, event_mask )
|
327
|
+
CZTop::HasFFIDelegate.raise_zmq_err if rc == -1
|
328
|
+
end
|
329
|
+
|
330
|
+
|
331
|
+
### Return the ZMQ bitmask for the events the specified +socket+ is registered
|
332
|
+
### for.
|
333
|
+
def mask_for( socket )
|
334
|
+
return self.sockets[ socket ][ :events ].inject( 0 ) do |mask, evt|
|
335
|
+
mask | VALID_EVENTS[ evt ]
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
### Return the low-level handle for +socket+. Raises an ArgumentError if argument is
|
341
|
+
### not a CZTop::Socket or a CZTop::Actor.
|
342
|
+
def ptr_for_socket( socket )
|
343
|
+
unless socket.is_a?( CZTop::Socket ) || socket.is_a?( CZTop::Actor )
|
344
|
+
raise ArgumentError, "expected a CZTop::Socket or a CZTop::Actor, got %p" % [ socket ]
|
345
|
+
end
|
346
|
+
return CZMQ::FFI::Zsock.resolve( socket )
|
347
|
+
end
|
348
|
+
|
349
|
+
end # class CZTop::Reactor
|
350
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'cztop'
|
5
|
+
require 'cztop/poller'
|
6
|
+
require 'cztop/reactor' unless defined?( CZTop::Reactor )
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
# Represents an event returned by {CZTop::Poller#wait}.
|
11
|
+
class CZTop::Reactor::Event
|
12
|
+
|
13
|
+
# Poll events in bitwise order
|
14
|
+
POLL_EVENTS = [
|
15
|
+
:read,
|
16
|
+
:write,
|
17
|
+
:err
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
### Create a new event from the specified +reactor+ and +event_ptr+.
|
22
|
+
def initialize( reactor, event_ptr )
|
23
|
+
@reactor = reactor
|
24
|
+
@poller_event = CZTop::Poller::ZMQ::PollerEvent.new( event_ptr )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
# The CZTop::Reactor that generated this event
|
30
|
+
attr_reader :reactor
|
31
|
+
|
32
|
+
|
33
|
+
### Get the Socket or Actor the event corresponds to.
|
34
|
+
def socket
|
35
|
+
return @socket ||= self.reactor.socket_for_ptr( @poller_event[:socket] )
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
### Returns +true+ if the event indicates the socket is readable.
|
40
|
+
def readable?
|
41
|
+
return @poller_event.readable?
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
### Returns +true+ if the event indicates the socket is writable.
|
46
|
+
def writable?
|
47
|
+
return @poller_event.writable?
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
### Return the poll events this event represents.
|
52
|
+
def poll_events
|
53
|
+
return POLL_EVENTS.select.with_index {|ev,i| @poller_event[:events][i].nonzero? }
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Return a human-readable string representation of the event suitable for
|
58
|
+
### debugging.
|
59
|
+
def inspect
|
60
|
+
return "#<%p:%#016x %p {%s}>" % [
|
61
|
+
self.class,
|
62
|
+
self.object_id * 2,
|
63
|
+
self.socket,
|
64
|
+
self.poll_events.map( &:to_s ).join( '/' )
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
end # class CZTop::Reactor::Event
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
require 'cztop/reactor/event'
|
6
|
+
|
7
|
+
|
8
|
+
describe CZTop::Reactor::Event do
|
9
|
+
|
10
|
+
DummyPollerEvent = Struct.new( :DummyPollerEvent, :readable?, :writable?, :socket, :events )
|
11
|
+
|
12
|
+
|
13
|
+
before( :each ) do
|
14
|
+
allow( CZTop::Poller::ZMQ::PollerEvent ).to receive( :new ).and_return( poller_event )
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
let( :reactor ) { CZTop::Reactor.new }
|
19
|
+
let( :poller_event ) do
|
20
|
+
DummyPollerEvent.new( false, false, nil, 0 )
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it "looks up the socket via the reactor it was created with" do
|
25
|
+
poller_event.socket = 0xDEADBEEF
|
26
|
+
event = described_class.new( reactor, 0xFADECAFE )
|
27
|
+
|
28
|
+
expect( reactor ).to receive( :socket_for_ptr ).with( 0xDEADBEEF ).and_return( :the_socket )
|
29
|
+
expect( event.socket ).to eq( :the_socket )
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
it "knows the poll event(s) that caused it" do
|
34
|
+
poller_event.events = CZTop::Poller::ZMQ::POLLIN|CZTop::Poller::ZMQ::POLLERR
|
35
|
+
event = described_class.new( reactor, 0xFADECAFE )
|
36
|
+
|
37
|
+
expect( event.poll_events ).to contain_exactly( :read, :err )
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,183 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../spec_helper'
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
require 'cztop/reactor'
|
8
|
+
|
9
|
+
describe CZTop::Reactor do
|
10
|
+
|
11
|
+
let( :reactor ) { described_class.new }
|
12
|
+
|
13
|
+
|
14
|
+
describe "socket registration" do
|
15
|
+
|
16
|
+
let( :socket ) { CZTop::Socket::REP.new }
|
17
|
+
|
18
|
+
|
19
|
+
it "allows a socket to be registered for reads" do
|
20
|
+
expect( reactor ).to_not be_registered( socket )
|
21
|
+
expect( reactor ).to_not have_event_enabled( socket, :read )
|
22
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
23
|
+
|
24
|
+
reactor.register( socket, :read ) {}
|
25
|
+
|
26
|
+
expect( reactor ).to be_registered( socket )
|
27
|
+
expect( reactor ).to have_event_enabled( socket, :read )
|
28
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "allows a socket to be registered for reads and writes" do
|
33
|
+
expect( reactor ).to_not be_registered( socket )
|
34
|
+
expect( reactor ).to_not have_event_enabled( socket, :read )
|
35
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
36
|
+
|
37
|
+
reactor.register( socket, :read, :write ) {}
|
38
|
+
|
39
|
+
expect( reactor ).to be_registered( socket )
|
40
|
+
expect( reactor ).to have_event_enabled( socket, :read )
|
41
|
+
expect( reactor ).to have_event_enabled( socket, :write )
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "errors if no block is given when registering a socket" do
|
46
|
+
expect {
|
47
|
+
reactor.register( socket, :read )
|
48
|
+
}.to raise_error( LocalJumpError, /no handler/i )
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
it "allows a registered socket to be unregistered" do
|
53
|
+
reactor.register( socket, :read, :write ) {}
|
54
|
+
reactor.unregister( socket )
|
55
|
+
|
56
|
+
expect( reactor ).to_not be_registered( socket )
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
it "doesn't error when unregistering an unregistered socket" do
|
61
|
+
expect {
|
62
|
+
reactor.unregister( socket )
|
63
|
+
}.to_not raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "allows a socket to have one or more events enabled for it after registration" do
|
68
|
+
reactor.register( socket ) {}
|
69
|
+
expect( reactor ).to_not have_event_enabled( socket, :read )
|
70
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
71
|
+
|
72
|
+
reactor.enable_events( socket, :read )
|
73
|
+
expect( reactor ).to have_event_enabled( socket, :read )
|
74
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
75
|
+
|
76
|
+
reactor.enable_events( socket, :write )
|
77
|
+
expect( reactor ).to have_event_enabled( socket, :read )
|
78
|
+
expect( reactor ).to have_event_enabled( socket, :write )
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
it "allows a socket to have one or more events disabled for it after registration" do
|
83
|
+
reactor.register( socket, :read, :write ) {}
|
84
|
+
expect( reactor ).to have_event_enabled( socket, :read )
|
85
|
+
expect( reactor ).to have_event_enabled( socket, :write )
|
86
|
+
|
87
|
+
reactor.disable_events( socket, :read )
|
88
|
+
expect( reactor ).to_not have_event_enabled( socket, :read )
|
89
|
+
expect( reactor ).to have_event_enabled( socket, :write )
|
90
|
+
|
91
|
+
reactor.disable_events( socket, :write )
|
92
|
+
expect( reactor ).to_not have_event_enabled( socket, :read )
|
93
|
+
expect( reactor ).to_not have_event_enabled( socket, :write )
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
it "can unregister all of its registered sockets" do
|
98
|
+
reactor.register( socket ) {}
|
99
|
+
reactor.register( CZTop::Socket::SUB.new ) {}
|
100
|
+
reactor.register( CZTop::Socket::REQ.new ) {}
|
101
|
+
|
102
|
+
reactor.clear
|
103
|
+
|
104
|
+
expect( reactor.sockets ).to be_empty
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
describe "timer registration" do
|
111
|
+
|
112
|
+
it "allows a callback to be called after a certain amount of time" do
|
113
|
+
handle = reactor.add_oneshot_timer( 5 ) {}
|
114
|
+
expect( reactor.timers ).to include( handle )
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
it "allows a callback to be called periodically on an interval" do
|
119
|
+
handle = reactor.add_periodic_timer( 5 ) {}
|
120
|
+
expect( reactor.timers ).to include( handle )
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
it "allows a timer to be cancelled" do
|
125
|
+
handle = reactor.add_periodic_timer( 5 ) {}
|
126
|
+
reactor.remove_timer( handle )
|
127
|
+
expect( reactor.timers ).to_not include( handle )
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
describe "monitors" do
|
134
|
+
|
135
|
+
it "can create and register a monitor for a socket" do
|
136
|
+
socket = CZTop::Socket::REP.new
|
137
|
+
begin
|
138
|
+
monitor = reactor.register_monitor( socket ) {}
|
139
|
+
expect( monitor ).to be_a( CZTop::Monitor )
|
140
|
+
expect( reactor ).to be_registered( monitor.actor )
|
141
|
+
ensure
|
142
|
+
monitor.terminate if monitor
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
describe "polling loop" do
|
150
|
+
|
151
|
+
it "processes events until it has no more sockets" do
|
152
|
+
reader = CZTop::Socket::PAIR.new( '@inproc://polling-test' )
|
153
|
+
writer = CZTop::Socket::PAIR.new( '>inproc://polling-test' )
|
154
|
+
|
155
|
+
data = nil
|
156
|
+
|
157
|
+
reactor.register( writer, :write ) do |ev|
|
158
|
+
ev.socket << "stuff"
|
159
|
+
reactor.unregister( writer )
|
160
|
+
end
|
161
|
+
reactor.register( reader, :read ) do |ev|
|
162
|
+
msg = ev.socket.receive
|
163
|
+
data = msg.frames.first.content
|
164
|
+
reactor.stop_polling
|
165
|
+
end
|
166
|
+
|
167
|
+
thr = Thread.new do
|
168
|
+
Thread.current.abort_on_exception = true
|
169
|
+
reactor.start_polling
|
170
|
+
end
|
171
|
+
|
172
|
+
thr.join( 2 )
|
173
|
+
thr.kill if thr.alive?
|
174
|
+
|
175
|
+
expect( data ).to eq( "stuff" )
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
end
|
183
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'simplecov' if ENV['COVERAGE']
|
5
|
+
|
6
|
+
require 'rspec'
|
7
|
+
|
8
|
+
require 'loggability/spechelpers'
|
9
|
+
|
10
|
+
|
11
|
+
### Mock with RSpec
|
12
|
+
RSpec.configure do |config|
|
13
|
+
config.run_all_when_everything_filtered = true
|
14
|
+
config.filter_run :focus
|
15
|
+
config.order = 'random'
|
16
|
+
config.mock_with( :rspec ) do |mock|
|
17
|
+
mock.syntax = :expect
|
18
|
+
end
|
19
|
+
|
20
|
+
config.include( Loggability::SpecHelpers )
|
21
|
+
end
|
22
|
+
|
23
|
+
|
metadata
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cztop-reactor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre20170316155217
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Granger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIEbDCCAtSgAwIBAgIBATANBgkqhkiG9w0BAQsFADA+MQwwCgYDVQQDDANnZWQx
|
14
|
+
GTAXBgoJkiaJk/IsZAEZFglGYWVyaWVNVUQxEzARBgoJkiaJk/IsZAEZFgNvcmcw
|
15
|
+
HhcNMTYwODIwMTgxNzQyWhcNMTcwODIwMTgxNzQyWjA+MQwwCgYDVQQDDANnZWQx
|
16
|
+
GTAXBgoJkiaJk/IsZAEZFglGYWVyaWVNVUQxEzARBgoJkiaJk/IsZAEZFgNvcmcw
|
17
|
+
ggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC/JWGRHO+USzR97vXjkFgt
|
18
|
+
83qeNf2KHkcvrRTSnR64i6um/ziin0I0oX23H7VYrDJC9A/uoUa5nGRJS5Zw/+wW
|
19
|
+
ENcvWVZS4iUzi4dsYJGY6yEOsXh2CcF46+QevV8iE+UmbkU75V7Dy1JCaUOyizEt
|
20
|
+
TH5UHsOtUU7k9TYARt/TgYZKuaoAMZZd5qyVqhF1vV+7/Qzmp89NGflXf2xYP26a
|
21
|
+
4MAX2qqKX/FKXqmFO+AGsbwYTEds1mksBF3fGsFgsQWxftG8GfZQ9+Cyu2+l1eOw
|
22
|
+
cZ+lPcg834G9DrqW2zhqUoLr1MTly4pqxYGb7XoDhoR7dd1kFE2a067+DzWC/ADt
|
23
|
+
+QkcqWUm5oh1fN0eqr7NsZlVJDulFgdiiYPQiIN7UNsii4Wc9aZqBoGcYfBeQNPZ
|
24
|
+
soo/6za/bWajOKUmDhpqvaiRv9EDpVLzuj53uDoukMMwxCMfgb04+ckQ0t2G7wqc
|
25
|
+
/D+K9JW9DDs3Yjgv9k4h7YMhW5gftosd+NkNC/+Y2CkCAwEAAaN1MHMwCQYDVR0T
|
26
|
+
BAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFHKN/nkRusdqCJEuq3lgB3fJvyTg
|
27
|
+
MBwGA1UdEQQVMBOBEWdlZEBGYWVyaWVNVUQub3JnMBwGA1UdEgQVMBOBEWdlZEBG
|
28
|
+
YWVyaWVNVUQub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAPJzKiT0zBU7kpqe0aS2qb
|
29
|
+
FI0PJ4y5I8buU4IZGUD5NEt/N7pZNfOyBxkrZkXhS44Fp+xwBH5ebLbq/WY78Bqd
|
30
|
+
db0z6ZgW4LMYMpWFfbXsRbd9TU2f52L8oMAhxOvF7Of5qJMVWuFQ8FPagk2iHrdH
|
31
|
+
inYLQagqAF6goWTXgAJCdPd6SNeeSNqA6vlY7CV1Jh5kfNJJ6xu/CVij1GzCLu/5
|
32
|
+
DMOr26DBv+qLJRRC/2h34uX71q5QgeOyxvMg+7V3u/Q06DXyQ2VgeeqiwDFFpEH0
|
33
|
+
PFkdPO6ZqbTRcLfNH7mFgCBJjsfSjJrn0sPBlYyOXgCoByfZnZyrIMH/UY+lgQqS
|
34
|
+
6Von1VDsfQm0eJh5zYZD64ZF86phSR7mUX3mXItwH04HrZwkWpvgd871DZVR3i1n
|
35
|
+
w8aNA5re5+Rt/Vvjxj5AcEnZnZiz5x959NaddQocX32Z1unHw44pzRNUur1GInfW
|
36
|
+
p4vpx2kUSFSAGjtCbDGTNV2AH8w9OU4xEmNz8c5lyoA=
|
37
|
+
-----END CERTIFICATE-----
|
38
|
+
date: 2017-03-16 00:00:00.000000000 Z
|
39
|
+
dependencies:
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: loggability
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.14'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.14'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: cztop
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.11'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.11'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: timers
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '4.1'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '4.1'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: hoe-mercurial
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1.4'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1.4'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: hoe-deveiate
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.8'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.8'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: hoe-highline
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0.2'
|
117
|
+
type: :development
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0.2'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: simplecov
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.13'
|
131
|
+
type: :development
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0.13'
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: rdoc-generator-fivefish
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0.3'
|
145
|
+
type: :development
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0.3'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: rdoc
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '4.0'
|
159
|
+
type: :development
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - "~>"
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '4.0'
|
166
|
+
- !ruby/object:Gem::Dependency
|
167
|
+
name: hoe
|
168
|
+
requirement: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - "~>"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '3.15'
|
173
|
+
type: :development
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '3.15'
|
180
|
+
description: |-
|
181
|
+
This is an implementation of the Reactor pattern described in [Pattern-Oriented
|
182
|
+
Software Architecture (Volume 2)][POSA2]. It allows an asynchronous application
|
183
|
+
to be described as one or more "reactions" to events, in this case either I/O
|
184
|
+
conditions on a ZMQ socket or a timer expiring.
|
185
|
+
|
186
|
+
A simple example:
|
187
|
+
|
188
|
+
# Start a SERVER socket, and print out any messages sent to it
|
189
|
+
reactor = CZTop::Reactor.new
|
190
|
+
socket = CZTop::Socket::SERVER.new
|
191
|
+
socket.bind( 'tcp://0.0.0.0:8' )
|
192
|
+
reactor.register( socket, :read ) do |event|
|
193
|
+
if event.readable?
|
194
|
+
message = event.socket.receive
|
195
|
+
puts "Read: %p" % [ message.to_a ]
|
196
|
+
end
|
197
|
+
end
|
198
|
+
reactor.start_polling
|
199
|
+
email:
|
200
|
+
- ged@FaerieMUD.org
|
201
|
+
executables: []
|
202
|
+
extensions: []
|
203
|
+
extra_rdoc_files:
|
204
|
+
- History.md
|
205
|
+
- LICENSE.txt
|
206
|
+
- Manifest.txt
|
207
|
+
- README.md
|
208
|
+
files:
|
209
|
+
- ".document"
|
210
|
+
- ".rdoc_options"
|
211
|
+
- ".simplecov"
|
212
|
+
- ChangeLog
|
213
|
+
- History.md
|
214
|
+
- LICENSE.txt
|
215
|
+
- Manifest.txt
|
216
|
+
- README.md
|
217
|
+
- Rakefile
|
218
|
+
- lib/cztop/reactor.rb
|
219
|
+
- lib/cztop/reactor/event.rb
|
220
|
+
- spec/cztop/reactor/event_spec.rb
|
221
|
+
- spec/cztop/reactor_spec.rb
|
222
|
+
- spec/spec_helper.rb
|
223
|
+
homepage: http://deveiate.org/projects/cztop-reactor
|
224
|
+
licenses:
|
225
|
+
- ISC
|
226
|
+
metadata: {}
|
227
|
+
post_install_message:
|
228
|
+
rdoc_options:
|
229
|
+
- "--main"
|
230
|
+
- README.md
|
231
|
+
require_paths:
|
232
|
+
- lib
|
233
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
234
|
+
requirements:
|
235
|
+
- - ">="
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: 2.2.4
|
238
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - ">"
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: 1.3.1
|
243
|
+
requirements: []
|
244
|
+
rubyforge_project:
|
245
|
+
rubygems_version: 2.6.8
|
246
|
+
signing_key:
|
247
|
+
specification_version: 4
|
248
|
+
summary: This is an implementation of the Reactor pattern described in [Pattern-Oriented
|
249
|
+
Software Architecture (Volume 2)][POSA2]
|
250
|
+
test_files: []
|