actor 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 +7 -0
- data/.gitignore +34 -0
- data/.travis.yml +10 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +14 -0
- data/actor.gemspec +24 -0
- data/gemfiles/Gemfile.travis +5 -0
- data/lib/actor.rb +11 -0
- data/lib/actor/base.rb +89 -0
- data/lib/actor/proxy.rb +46 -0
- data/lib/actor/timer.rb +46 -0
- data/lib/actor/version.rb +5 -0
- data/spec/base_spec.rb +82 -0
- data/spec/proxy_spec.rb +27 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/timer_spec.rb +60 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 692bbec40fb07dccc05300db738a52d61779194d
|
4
|
+
data.tar.gz: fd21f5720cca18985abda493c3bdc55886b4ec71
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 27afa42688e8e9608dac5ecbffda7a677ef46d94ab192f5dbe8f9b10c88ab1f2a2ca5d0be40844e22fe603ecffa8806459602a8ad556053f5c8ec6282de07b59
|
7
|
+
data.tar.gz: 975d6e1f537467f2cd21c2efe9397c9dd0519337ed236b163ad6da55a6cf59a654b366bd4b4b59b4a08e0bfd28e4429a197fadff45c5a39572506f21ed681cf6
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
/html/
|
23
|
+
|
24
|
+
## Environment normalisation:
|
25
|
+
/.bundle/
|
26
|
+
/lib/bundler/man/
|
27
|
+
|
28
|
+
Gemfile.lock
|
29
|
+
.ruby-version
|
30
|
+
.ruby-gemset
|
31
|
+
|
32
|
+
.rvmrc
|
33
|
+
|
34
|
+
.idea/
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Max
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# Actor
|
2
|
+
|
3
|
+
[](https://travis-ci.org/maxgale/actor)
|
4
|
+
|
5
|
+
The actor gem provides a completely implicit implementation of the actor pattern. The goal of the library is to provide
|
6
|
+
an easy way to implement fast, concurrent code without having to worry about race conditions or unexpected side-effects.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'actor'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install actor
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Actors
|
25
|
+
|
26
|
+
require 'actor/base'
|
27
|
+
|
28
|
+
class MyActor
|
29
|
+
include Actor::Base
|
30
|
+
|
31
|
+
def example_method
|
32
|
+
'hi'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
my_actor = MyActor.new
|
37
|
+
|
38
|
+
That's it! Now any interations with `my_actor` will be executed concurrently. It's also worth noting that there are no
|
39
|
+
return values. Instead, code is executed via message passing. Instead, there are callbacks.
|
40
|
+
|
41
|
+
### Callbacks
|
42
|
+
|
43
|
+
my_actor.before_action :example_method do
|
44
|
+
# Code here is executed before :example_method is executed by the actor
|
45
|
+
puts 'before'
|
46
|
+
end
|
47
|
+
|
48
|
+
my_actor.after_action :example_method do |result|
|
49
|
+
# Code here is executed after :example_method is executed by the actor
|
50
|
+
puts 'after'
|
51
|
+
puts result
|
52
|
+
end
|
53
|
+
|
54
|
+
my_actor.example_method
|
55
|
+
|
56
|
+
=> before
|
57
|
+
=> after
|
58
|
+
=> hi
|
59
|
+
|
60
|
+
### Timers
|
61
|
+
|
62
|
+
Another useful feature is the timer. The timer is an object that periodically executes a block of code.
|
63
|
+
|
64
|
+
require 'actor/timer'
|
65
|
+
|
66
|
+
# Create a timer the executes every 1/30th of a second. Only executes twice.
|
67
|
+
Actor::Timer.new 0.033, 2 do
|
68
|
+
puts 'hi'
|
69
|
+
end
|
70
|
+
|
71
|
+
=> hi
|
72
|
+
=> hi
|
73
|
+
|
74
|
+
Passing in `0` as the number of iterations to the timer causes it to execute indefinitely. You can also pause, resume,
|
75
|
+
and wait for timers to finish execution.
|
76
|
+
|
77
|
+
my_timer = Timer.new 0.033, 0 do
|
78
|
+
# Do periodic work
|
79
|
+
end
|
80
|
+
|
81
|
+
my_timer.pause # Temporarily stop work
|
82
|
+
|
83
|
+
my_timer.resume # Resume the work
|
84
|
+
|
85
|
+
my_timer.wait # Since `iterations = 0`, this will block forever
|
86
|
+
|
87
|
+
## Gotchas
|
88
|
+
|
89
|
+
Unfortunately, there are a few "gotchas" when using this gem.
|
90
|
+
|
91
|
+
1. `Actor::Base` overrides the including class's `:new` method and renames it to `:__actor_new`. This sets up the
|
92
|
+
possibility of naming conflicts.
|
93
|
+
1. `Actor::Base` overrides `:send`, making it impossible to use `:send` without concurrent execution and callbacks.
|
94
|
+
1. All actors are wrapped in a proxy class. This proxy forwards all methods to the
|
95
|
+
instance to be executed in a concurrent way. This means it is impossible to execute code normally when handling the
|
96
|
+
proxy. You can access the underlying instance by calling `:__proxy_target` on the proxy. Note: No callbacks are not
|
97
|
+
triggered when accessing the instance directly (unless you use send).
|
98
|
+
1. The timer period is counted from the end of the last block to the start of the next block. This means that the timer
|
99
|
+
firing is very approximate and definitely not designed for blocking code.
|
100
|
+
|
101
|
+
## Todo
|
102
|
+
|
103
|
+
1. Using a thread pool would be nice.
|
104
|
+
2. Benchmarks comparing MRI 2.1.2 vs. Rubinisu 2.2.6.
|
105
|
+
3. Benchmarks comparing of this gem vs. Celluloid
|
106
|
+
5. Add a way of using actors over a network via RPC
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
1. Fork it ( https://github.com/maxgale/actor/fork )
|
111
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
112
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
113
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
114
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rdoc/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new :spec do |task|
|
6
|
+
task.rspec_opts = %w(--color --format nested)
|
7
|
+
end
|
8
|
+
|
9
|
+
RDoc::Task.new :rdoc do |rdoc|
|
10
|
+
rdoc.rdoc_files.include "lib/**/*.rb"
|
11
|
+
rdoc.options << "--all"
|
12
|
+
end
|
13
|
+
|
14
|
+
task default: [:spec, :rdoc]
|
data/actor.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'actor/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "actor"
|
8
|
+
spec.version = Actor::VERSION
|
9
|
+
spec.authors = ["Max Gale"]
|
10
|
+
spec.email = ["maxgale4@gmail.com"]
|
11
|
+
spec.summary = %q{A simple implementation of the actor pattern}
|
12
|
+
spec.homepage = "https://github.com/maxgale/actor"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
22
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
23
|
+
spec.add_development_dependency "rdoc", "~> 4.1"
|
24
|
+
end
|
data/lib/actor.rb
ADDED
data/lib/actor/base.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'actor/proxy'
|
3
|
+
|
4
|
+
module Actor
|
5
|
+
##
|
6
|
+
# Module that actors should include
|
7
|
+
module Base
|
8
|
+
##
|
9
|
+
# Adds a new message to the queue. Args and block are optional
|
10
|
+
#
|
11
|
+
# * *Args*:
|
12
|
+
# - +method_name+: symbol representation of the action (method) to preform
|
13
|
+
# - +args+: the arguments to pass to the action when it is executed
|
14
|
+
# - +block+: the block to pass to the action when it is executed
|
15
|
+
def send method_name, *args, &block
|
16
|
+
@mailbox << [method_name, args, block]
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Adds a listener that is called before the including object performs
|
21
|
+
# the specified action.
|
22
|
+
#
|
23
|
+
# * *Args*:
|
24
|
+
# - +action+: the action (symbol) the hook into
|
25
|
+
# - +block+: the block to execute before the specified action
|
26
|
+
def before_action action, &block
|
27
|
+
@audience[:before][action] ||= Set.new
|
28
|
+
@audience[:before][action] << block
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Adds a listener that is called after the including object performs
|
33
|
+
# the specified action.
|
34
|
+
#
|
35
|
+
# * *Args*:
|
36
|
+
# - +action+: the action the hook into
|
37
|
+
# - +block+: the block to execute after the specified action
|
38
|
+
# * *Yields*: the value returned by the action
|
39
|
+
def after_action action, &block
|
40
|
+
@audience[:after][action] ||= Set.new
|
41
|
+
@audience[:after][action] << block
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Adds a hook before object initialization to automatically sets up the
|
46
|
+
# thread that executes actions and sets up the callback data structures.
|
47
|
+
#
|
48
|
+
# * *Args*:
|
49
|
+
# - +klass+: the class whose initialization is hooked into.
|
50
|
+
def self.included klass
|
51
|
+
class << klass
|
52
|
+
alias_method :__actor_new, :new
|
53
|
+
|
54
|
+
##
|
55
|
+
# Hooks into the initialization of the object to initialize the
|
56
|
+
# mailbox. Also starts the thread executing async method calls
|
57
|
+
def new *args
|
58
|
+
instance = __actor_new *args
|
59
|
+
instance.instance_variable_set :@audience, {}
|
60
|
+
instance.instance_variable_set :@mailbox, Queue.new
|
61
|
+
|
62
|
+
audience = {}
|
63
|
+
audience[:before] = {}
|
64
|
+
audience[:after] = {}
|
65
|
+
instance.instance_variable_set :@audience, audience
|
66
|
+
|
67
|
+
Thread.new do
|
68
|
+
loop do
|
69
|
+
mailbox = instance.instance_variable_get :@mailbox
|
70
|
+
method_name, args, block = mailbox.pop
|
71
|
+
|
72
|
+
if audience[:before][method_name]
|
73
|
+
audience[:before][method_name].each { |callback| callback.call }
|
74
|
+
end
|
75
|
+
|
76
|
+
result = instance.method(method_name).call *args, &block
|
77
|
+
|
78
|
+
if audience[:after][method_name]
|
79
|
+
audience[:after][method_name].each { |callback| callback.yield result }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
Proxy.new instance
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/actor/proxy.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Actor
|
2
|
+
##
|
3
|
+
# The proxy class wraps an object and invokes all of its method using :send
|
4
|
+
class Proxy < BasicObject
|
5
|
+
##
|
6
|
+
# Create a new proxy
|
7
|
+
#
|
8
|
+
# * *Args*:
|
9
|
+
# - +proxy_target+: the instance to proxy
|
10
|
+
def initialize proxy_target
|
11
|
+
@proxy_target = proxy_target
|
12
|
+
end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Proxies the method call to the proxy target using :send
|
16
|
+
def method_missing name, *args, &block
|
17
|
+
@proxy_target.send name, *args, &block
|
18
|
+
end
|
19
|
+
|
20
|
+
##
|
21
|
+
# Get the proxy target of this proxy
|
22
|
+
#
|
23
|
+
# * *Returns*: the proxy target
|
24
|
+
def __proxy_target
|
25
|
+
@proxy_target
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Overrides the proxy equals to pass the equality check to the proxy target
|
30
|
+
def == other
|
31
|
+
@proxy_target == other
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Overrides the proxy equals to pass the equality check to the proxy target
|
36
|
+
def != other
|
37
|
+
@proxy_target != other
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Overrides the proxy equals to pass the equality check to the proxy target
|
42
|
+
def equal? other
|
43
|
+
@proxy_target.equal? other
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/actor/timer.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Actor
|
2
|
+
##
|
3
|
+
# Simple timer implementation
|
4
|
+
class Timer
|
5
|
+
##
|
6
|
+
# Create a new timer that fires every <period> seconds. The number of
|
7
|
+
# iterations specifies how many times the timer should fire.
|
8
|
+
#
|
9
|
+
# * *Args*:
|
10
|
+
# - +period+: the time, in seconds, between firing the timer.
|
11
|
+
# - +iterations+: the number times the timer should fire. 0 iterations
|
12
|
+
# means fire infinitely
|
13
|
+
def initialize period, iterations, &block
|
14
|
+
@pause_queue = Queue.new
|
15
|
+
|
16
|
+
i = iterations == 0 ? 1.0 / 0.0 : iterations
|
17
|
+
@timer_thread = Thread.new do
|
18
|
+
(1..i).step do
|
19
|
+
sleep unless @pause_queue.empty?
|
20
|
+
block.call
|
21
|
+
sleep period
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Pauses the timer. The currently executing iteration is finished before
|
28
|
+
# the time is paused
|
29
|
+
def pause
|
30
|
+
@pause_queue << :paused
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Resumes the timer
|
35
|
+
def resume
|
36
|
+
@pause_queue.clear
|
37
|
+
@timer_thread.wakeup
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Block the current thread until the timer has finished executing
|
42
|
+
def wait
|
43
|
+
@timer_thread.join
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Actor::Base do
|
4
|
+
before :each do
|
5
|
+
@actor_class = Class.new do
|
6
|
+
include Actor::Base
|
7
|
+
|
8
|
+
def action
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'initializes mailbox on creation' do
|
14
|
+
proxy = @actor_class.new
|
15
|
+
actor = proxy.__proxy_target
|
16
|
+
|
17
|
+
expect(actor.instance_variable_get :@mailbox).not_to be nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'initializes mailbox on creation of subclass' do
|
21
|
+
child_actor = Class.new(@actor_class).new.__proxy_target
|
22
|
+
expect(child_actor.instance_variable_get :@mailbox).not_to be nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'receives proxied messages' do
|
26
|
+
Thread.stub :new
|
27
|
+
|
28
|
+
proxy = @actor_class.new
|
29
|
+
actor = proxy.__proxy_target
|
30
|
+
|
31
|
+
proxy.message1
|
32
|
+
proxy.message2 :arg1, :arg2
|
33
|
+
|
34
|
+
messages = actor.instance_variable_get :@mailbox
|
35
|
+
expect(messages.size).to be 2
|
36
|
+
expect(messages.pop).to eq [:message1, [], nil]
|
37
|
+
expect(messages.pop).to eq [:message2, [:arg1, :arg2], nil]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'processes messages' do
|
41
|
+
proxy = @actor_class.new
|
42
|
+
actor = proxy.__proxy_target
|
43
|
+
|
44
|
+
actor.should_receive :message1
|
45
|
+
actor.should_receive(:message2).with :arg1
|
46
|
+
actor.should_receive(:message3).with :arg1, :arg2
|
47
|
+
|
48
|
+
proxy.message1
|
49
|
+
proxy.message2 :arg1
|
50
|
+
proxy.message3 :arg1, :arg2
|
51
|
+
|
52
|
+
# Sleep because threads are lame.
|
53
|
+
sleep 0.1
|
54
|
+
|
55
|
+
messages = actor.instance_variable_get :@mailbox
|
56
|
+
expect(messages.size).to be 0
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'executes before/after callbacks' do
|
60
|
+
proxy = @actor_class.new
|
61
|
+
actor = proxy.__proxy_target
|
62
|
+
|
63
|
+
proxy.before_action :action do
|
64
|
+
actor.before_method
|
65
|
+
end
|
66
|
+
|
67
|
+
ret_value = nil
|
68
|
+
proxy.after_action :action do |result|
|
69
|
+
ret_value = result
|
70
|
+
actor.after_method
|
71
|
+
end
|
72
|
+
|
73
|
+
actor.should_receive(:before_method).ordered
|
74
|
+
actor.should_receive(:action).ordered.and_return('ret_value')
|
75
|
+
actor.should_receive(:after_method).ordered
|
76
|
+
|
77
|
+
proxy.action
|
78
|
+
sleep 0.1
|
79
|
+
|
80
|
+
expect(ret_value).to eq 'ret_value'
|
81
|
+
end
|
82
|
+
end
|
data/spec/proxy_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Actor::Proxy do
|
4
|
+
before :each do
|
5
|
+
@target = Object.new
|
6
|
+
@proxy = Actor::Proxy.new @target
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'sets the proxy target' do
|
10
|
+
expect(@proxy.instance_eval '@proxy_target').to be @target
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'preserves equality' do
|
14
|
+
expect(@proxy).to eq @target
|
15
|
+
expect(@proxy != @target).to be false
|
16
|
+
expect(@proxy.equal? @target).to be true
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'gets the proxy target' do
|
20
|
+
expect(@proxy.__proxy_target).to be @target
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'forwards messages via send' do
|
24
|
+
@target.should_receive(:send).with :method, :to_s
|
25
|
+
@proxy.method :to_s
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/timer_spec.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Actor::Timer do
|
4
|
+
it 'executes for the given number of iterations' do
|
5
|
+
iterations = 0
|
6
|
+
Actor::Timer.new 0.033, 30 do
|
7
|
+
iterations += 1
|
8
|
+
end.wait
|
9
|
+
|
10
|
+
expect(iterations).to be 30
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'fires every period' do
|
14
|
+
deltas = []
|
15
|
+
last_time = Time.now - 0.033
|
16
|
+
|
17
|
+
Actor::Timer.new 0.033, 30 do
|
18
|
+
deltas << (Time.now - last_time).to_f
|
19
|
+
last_time = Time.now
|
20
|
+
end.wait
|
21
|
+
|
22
|
+
deltas.each { |delta| expect(delta).to be_within(0.01).of(0.033) }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'pauses and resumes' do
|
26
|
+
long_pointless_sum = 0
|
27
|
+
timer = Actor::Timer.new 0.033, 3000 do
|
28
|
+
long_pointless_sum += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
timer.pause
|
32
|
+
timer.pause # Verify that two messages get queued
|
33
|
+
post_pause_sum = long_pointless_sum
|
34
|
+
sleep 1
|
35
|
+
|
36
|
+
pause_queue = timer.instance_variable_get(:@pause_queue)
|
37
|
+
|
38
|
+
expect(long_pointless_sum).to be post_pause_sum
|
39
|
+
expect(timer.instance_variable_get(:@timer_thread).status).to eq 'sleep'
|
40
|
+
expect(pause_queue.pop).to be :paused
|
41
|
+
expect(pause_queue.pop).to be :paused
|
42
|
+
expect(pause_queue).to be_empty
|
43
|
+
|
44
|
+
pause_queue.should_receive :clear
|
45
|
+
timer.resume
|
46
|
+
sleep 1
|
47
|
+
|
48
|
+
expect(long_pointless_sum).to be > post_pause_sum
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'blocks the calling thread when waiting' do
|
52
|
+
side_effect = false
|
53
|
+
Actor::Timer.new 0.01, 1 do
|
54
|
+
sleep 1
|
55
|
+
side_effect = true
|
56
|
+
end.wait
|
57
|
+
|
58
|
+
expect(side_effect).to be true
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: actor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Max Gale
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2014-05-12 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
prerelease: false
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "1.6"
|
22
|
+
type: :development
|
23
|
+
version_requirements: *id001
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rake
|
26
|
+
prerelease: false
|
27
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "10.3"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id002
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rspec
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: "2.14"
|
42
|
+
type: :development
|
43
|
+
version_requirements: *id003
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: rdoc
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ~>
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "4.1"
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id004
|
54
|
+
description:
|
55
|
+
email:
|
56
|
+
- maxgale4@gmail.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files: []
|
62
|
+
|
63
|
+
files:
|
64
|
+
- .gitignore
|
65
|
+
- .travis.yml
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- actor.gemspec
|
71
|
+
- gemfiles/Gemfile.travis
|
72
|
+
- lib/actor.rb
|
73
|
+
- lib/actor/base.rb
|
74
|
+
- lib/actor/proxy.rb
|
75
|
+
- lib/actor/timer.rb
|
76
|
+
- lib/actor/version.rb
|
77
|
+
- spec/base_spec.rb
|
78
|
+
- spec/proxy_spec.rb
|
79
|
+
- spec/spec_helper.rb
|
80
|
+
- spec/timer_spec.rb
|
81
|
+
homepage: https://github.com/maxgale/actor
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- &id005
|
94
|
+
- ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- *id005
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.2.2
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: A simple implementation of the actor pattern
|
107
|
+
test_files:
|
108
|
+
- spec/base_spec.rb
|
109
|
+
- spec/proxy_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
- spec/timer_spec.rb
|