muscle 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.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.textile +47 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/example/api_example.rb +15 -0
- data/lib/muscle.rb +102 -0
- data/muscle.gemspec +47 -0
- data/spec/muscle_spec.rb +95 -0
- data/spec/spec_helper.rb +10 -0
- metadata +67 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Daniel Neighman
|
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/README.textile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
h1. Muscle
|
2
|
+
|
3
|
+
A very simple library for parallelizing slow actions.
|
4
|
+
Built with api access in mind, where you need to access an external service which may take a while.
|
5
|
+
|
6
|
+
Within web frameworks, using muscle you can setup your api/external requests and they will be fetched in the background.
|
7
|
+
You are free to move on with rendering output while the action is executed in the background. Muscle will only block when
|
8
|
+
you ask for the result of an action that has not yet completed. This is not intended to replace a background queue system like rabbitmq or delayed-job. The intention of muscle is that it's more for when you need to bring data in from an external source, rather than push data out.
|
9
|
+
|
10
|
+
For example:
|
11
|
+
|
12
|
+
<pre><code>
|
13
|
+
m = Muscle.new do |m|
|
14
|
+
m.action(:action_name) do
|
15
|
+
# some slow action
|
16
|
+
# the result of the block is returned as the value of the action
|
17
|
+
end
|
18
|
+
|
19
|
+
m.action(:another, :timeout => 1.2) do
|
20
|
+
# some unreliable action.
|
21
|
+
end
|
22
|
+
|
23
|
+
# Setup a special timeout handler for the second action
|
24
|
+
# by default timeouts are set to 5 seconds
|
25
|
+
m.on_timeout(:another) do
|
26
|
+
"Sorry but :action timed out"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
</code></pre>
|
30
|
+
|
31
|
+
Actions start executing as soon as they're declared, but don't block until they're accessed.
|
32
|
+
Since no timeout handler is setup on :action_name when accessed it will raise a Timeout::Error if it has timed out.
|
33
|
+
|
34
|
+
<pre><code>
|
35
|
+
# Get the results of a single action
|
36
|
+
m[:action_name] #<-- will block for the result if not yet finished
|
37
|
+
|
38
|
+
# Iterate through all actions in declared order
|
39
|
+
m.each do |result|
|
40
|
+
# process results
|
41
|
+
end
|
42
|
+
</code></pre>
|
43
|
+
|
44
|
+
|
45
|
+
== Copyright
|
46
|
+
|
47
|
+
Copyright (c) 2009 Daniel Neighman. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "muscle"
|
8
|
+
gem.summary = "Muscle is a simple parralellizer for slow actions"
|
9
|
+
gem.email = "has.sox@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/hassox/muscle"
|
11
|
+
gem.authors = ["Daniel Neighman"]
|
12
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
|
+
end
|
14
|
+
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'spec/rake/spectask'
|
20
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
21
|
+
spec.libs << 'lib' << 'spec'
|
22
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
23
|
+
end
|
24
|
+
|
25
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
28
|
+
spec.rcov = true
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION.yml')
|
37
|
+
config = YAML.load(File.read('VERSION.yml'))
|
38
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "muscle #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "muscle")
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
@m = Muscle.new do |m|
|
5
|
+
m.action(:github) do
|
6
|
+
Net::HTTP.start("github.com"){|h| h.get("/")}
|
7
|
+
end
|
8
|
+
m.action(:google) do
|
9
|
+
Net::HTTP.start("google.com"){|h| h.get("/")}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# actions are threaded and fetched in the background. Blocking does not occur until they are accessed
|
14
|
+
|
15
|
+
@m[:github] # <-- blocks until the page is loaded
|
data/lib/muscle.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
class Muscle
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@threads, @values, @names, @timeouts = {}, {}, [], {}
|
7
|
+
yield self
|
8
|
+
end
|
9
|
+
|
10
|
+
# Use this to declare actions for the muscle to perform
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# m = Muscle.new do |m|
|
15
|
+
# m.action(:github) do
|
16
|
+
# Net::HTTP.start("github.com"){|h| h.get("/")}
|
17
|
+
# end
|
18
|
+
# m.action(:failblog) do
|
19
|
+
# Net::HTTP.start("failblog.com"){|h| h.get("/")}
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# This will setup a muscle to fetch the page from github.com/ and failblog.com/
|
24
|
+
# and make them available in the muscle. The pages are fetched in the background
|
25
|
+
# and will not block until you access the results of the action
|
26
|
+
#
|
27
|
+
# options -
|
28
|
+
# +timeout+ A default timeout of 5 seconds is included. Set this option for custom timeouts
|
29
|
+
#
|
30
|
+
# :api: pulbic
|
31
|
+
def action(name = random_name, opts = {}, &block)
|
32
|
+
opts[:timeout] ||= 5
|
33
|
+
@names << name
|
34
|
+
@threads[name] = Thread.new{Timeout::timeout(opts[:timeout], &block)}
|
35
|
+
name
|
36
|
+
end
|
37
|
+
|
38
|
+
# Use this to set a timeout on a given action, or on all actions
|
39
|
+
#
|
40
|
+
# Example
|
41
|
+
# m.on_timeout(:foo){|name| "#{name} timed out"}
|
42
|
+
# m.on_timeout(:bar){|name| "#{name} timed out"}
|
43
|
+
#
|
44
|
+
# This example sets a return value for timed out actions and replaces the exception with the
|
45
|
+
# results of the block. If no timeout is set, the original timeout exception is returned.
|
46
|
+
#
|
47
|
+
# You can also mass declare on_timeout hooks to respond in the same way.
|
48
|
+
# The above example would compress to
|
49
|
+
# m.on_timeout(:foo, :bar){|name| "#{name} timed out"}
|
50
|
+
#
|
51
|
+
# You can also setup a catch all timeout response as a fall back like this
|
52
|
+
#
|
53
|
+
# m.on_timeout{|name| "#{name} timed out"}
|
54
|
+
#
|
55
|
+
# You can mix and match as many on_timeout handlers as you need. Named handlers will take precedence
|
56
|
+
# over the non-named handlers
|
57
|
+
#
|
58
|
+
# :api: public
|
59
|
+
def on_timeout(*names, &block)
|
60
|
+
names = [:any] if names.empty?
|
61
|
+
names.each do |n|
|
62
|
+
@timeouts[n] = block
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Access the results of the slow action
|
67
|
+
# if the action is not yet completed, the process will block until it's done.
|
68
|
+
#
|
69
|
+
# :api: public
|
70
|
+
def [](name)
|
71
|
+
return @values[name] unless @values[name].nil?
|
72
|
+
begin
|
73
|
+
if @threads[name]
|
74
|
+
@values[name] = @threads[name].join.value
|
75
|
+
@threads.delete(name)
|
76
|
+
end
|
77
|
+
@values[name]
|
78
|
+
rescue Timeout::Error => e
|
79
|
+
if to = (@timeouts[name] || @timeouts[:any])
|
80
|
+
to.call(name)
|
81
|
+
else
|
82
|
+
raise e
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Iterate through the results of each action in declared order.
|
88
|
+
# Will block on any uncompleted action
|
89
|
+
#
|
90
|
+
# :api: public
|
91
|
+
def each
|
92
|
+
@names.each{|n| yield self[n]}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
# provides a random name to an action if one was not specified
|
97
|
+
def random_name
|
98
|
+
letters ||= ("a".."z").to_a + ("A".."Z").to_a
|
99
|
+
(0..15).inject(""){ |out,i| out << letters[rand(letters.length - 1)] }
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/muscle.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{muscle}
|
5
|
+
s.version = "0.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Daniel Neighman"]
|
9
|
+
s.date = %q{2009-07-18}
|
10
|
+
s.email = %q{has.sox@gmail.com}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"LICENSE",
|
13
|
+
"README.textile"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".document",
|
17
|
+
".gitignore",
|
18
|
+
"LICENSE",
|
19
|
+
"README.textile",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION",
|
22
|
+
"example/api_example.rb",
|
23
|
+
"lib/muscle.rb",
|
24
|
+
"muscle.gemspec",
|
25
|
+
"spec/muscle_spec.rb",
|
26
|
+
"spec/spec_helper.rb"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/hassox/muscle}
|
29
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
s.rubygems_version = %q{1.3.4}
|
32
|
+
s.summary = %q{Muscle is a simple parralellizer for slow actions}
|
33
|
+
s.test_files = [
|
34
|
+
"spec/muscle_spec.rb",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
|
38
|
+
if s.respond_to? :specification_version then
|
39
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
40
|
+
s.specification_version = 3
|
41
|
+
|
42
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
43
|
+
else
|
44
|
+
end
|
45
|
+
else
|
46
|
+
end
|
47
|
+
end
|
data/spec/muscle_spec.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
describe "Muscle" do
|
5
|
+
|
6
|
+
it "should allow me to setup many calls in threads" do
|
7
|
+
start_time = Time.now.to_f
|
8
|
+
m = Muscle.new do |m|
|
9
|
+
m.action(:foo) do
|
10
|
+
sleep 1
|
11
|
+
:foo
|
12
|
+
end
|
13
|
+
m.action(:bar) do
|
14
|
+
0.5
|
15
|
+
:bar
|
16
|
+
end
|
17
|
+
m.action(:baz) do
|
18
|
+
sleep 1
|
19
|
+
:baz
|
20
|
+
end
|
21
|
+
end # Muscle.new
|
22
|
+
end_time = Time.now.to_f
|
23
|
+
(end_time - start_time).should < 1.2
|
24
|
+
|
25
|
+
m[:foo].should == :foo
|
26
|
+
m[:bar].should == :bar
|
27
|
+
m[:baz].should == :baz
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should walk through the output of the actions in declared order" do
|
31
|
+
m = Muscle.new do |m|
|
32
|
+
m.action(:foo) do
|
33
|
+
sleep 0.25
|
34
|
+
:foo
|
35
|
+
end
|
36
|
+
m.action(:bar) do
|
37
|
+
sleep 0.1
|
38
|
+
:bar
|
39
|
+
end
|
40
|
+
m.action do
|
41
|
+
:unamed
|
42
|
+
end
|
43
|
+
end #{ Muscle
|
44
|
+
m.map{|f| f}.should == [:foo, :bar, :unamed]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow me to set a timeout on a particular action" do
|
48
|
+
m = Muscle.new do |m|
|
49
|
+
m.action(:foo, :timeout => 0.1) do
|
50
|
+
sleep 2
|
51
|
+
end
|
52
|
+
end
|
53
|
+
lambda do
|
54
|
+
m[:foo]
|
55
|
+
end.should raise_error(Timeout::Error)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should allow me to set a timeout result" do
|
59
|
+
m = Muscle.new do |m|
|
60
|
+
m.action(:foo, :timeout => 0.1) do
|
61
|
+
sleep 2
|
62
|
+
end
|
63
|
+
|
64
|
+
m.on_timeout(:foo) do |name|
|
65
|
+
"#{name.inspect} Timed Out"
|
66
|
+
end
|
67
|
+
end # Muscle
|
68
|
+
m[:foo].should == ":foo Timed Out"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "Should allow me to setup a catch all timeout" do
|
72
|
+
m = Muscle.new do |m|
|
73
|
+
m.action(:foo, :timeout => 0.1){sleep 2}
|
74
|
+
m.action(:bar, :timeout => 0.1){sleep 2}
|
75
|
+
m.on_timeout do |name|
|
76
|
+
"#{name.inspect} timed out"
|
77
|
+
end
|
78
|
+
end # Muscle
|
79
|
+
m[:foo].should == ":foo timed out"
|
80
|
+
m[:bar].should == ":bar timed out"
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should allow me to setup an on_timeout for many actions" do
|
84
|
+
m = Muscle.new do |m|
|
85
|
+
m.action(:foo, :timeout => 0.1){sleep 2}
|
86
|
+
m.action(:bar, :timeout => 0.1){sleep 2}
|
87
|
+
m.action(:baz, :timeout => 0.1){sleep 2}
|
88
|
+
m.on_timeout(:foo, :baz){|n| "Special: #{n.inspect} timed out"}
|
89
|
+
m.on_timeout{|n| "Plain: #{n.inspect} timed out"}
|
90
|
+
end # Muscle
|
91
|
+
m[:foo].should == "Special: :foo timed out"
|
92
|
+
m[:baz].should == "Special: :baz timed out"
|
93
|
+
m[:bar].should == "Plain: :bar timed out"
|
94
|
+
end
|
95
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: muscle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Neighman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-07-18 00:00:00 +10:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: has.sox@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.textile
|
25
|
+
files:
|
26
|
+
- .document
|
27
|
+
- .gitignore
|
28
|
+
- LICENSE
|
29
|
+
- README.textile
|
30
|
+
- Rakefile
|
31
|
+
- VERSION
|
32
|
+
- example/api_example.rb
|
33
|
+
- lib/muscle.rb
|
34
|
+
- muscle.gemspec
|
35
|
+
- spec/muscle_spec.rb
|
36
|
+
- spec/spec_helper.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/hassox/muscle
|
39
|
+
licenses: []
|
40
|
+
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options:
|
43
|
+
- --charset=UTF-8
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Muscle is a simple parralellizer for slow actions
|
65
|
+
test_files:
|
66
|
+
- spec/muscle_spec.rb
|
67
|
+
- spec/spec_helper.rb
|