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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
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.
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,10 @@
1
+ require 'spec'
2
+
3
+ # $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ # $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+
6
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'muscle')
7
+
8
+ Spec::Runner.configure do |config|
9
+
10
+ end
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