parallizer 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Parallizer - Execute your service layer in parallel
2
2
 
3
- Parallizer executes service methods in parallel, stores the method results, then creates a proxy with those results for your service. Your application then uses the short-lived service proxy (think of a single request for a web application) and executes your methods without again calling the underlying implementation. For applications that make considerable use of web service calls, Parallizer can give you a considerable performance boost.
3
+ Parallizer executes service methods in parallel, stores the method results, and creates a proxy of your service with those results. Your application then uses the short-lived service proxy (think of a single request for a web application) and calls your methods without again executing the underlying implementation. For applications that make considerable use of web service calls, Parallizer can give you a considerable performance boost.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,51 +13,60 @@ Parallizer executes service methods in parallel, stores the method results, then
13
13
  Here's an example service.
14
14
 
15
15
  ```ruby
16
+ require 'rubygems'
16
17
  require 'net/http'
18
+ require 'nokogiri'
17
19
 
18
20
  class SearchService
19
- def search_result_for_foo
20
- Net::HTTP.get('www.google.com', '/?q=foo')
21
+ def top_urls_for_foo
22
+ parse_search_result_for_urls(Net::HTTP.get('www.google.com', '/search?q=foo'))
21
23
  end
22
24
 
23
- def search_result_for_bar
24
- Net::HTTP.get('www.google.com', '/?q=foo')
25
+ def top_urls_for_bar
26
+ parse_search_result_for_urls(Net::HTTP.get('www.google.com', '/search?q=bar'))
27
+ end
28
+
29
+ private
30
+
31
+ def parse_search_result_for_urls(content)
32
+ Nokogiri::HTML.parse(content).search('h3.r > a').collect(&:attributes).collect{ |attrs| attrs['href'].value }
25
33
  end
26
34
  end
27
35
 
28
36
  $search_service = SearchService.new
29
37
  ```
30
38
 
31
- Now create a Parallizer for that service and add all of the methods you intend to call. Then execute the service methods in parallel and return a service proxy that has the stored results of the method calls.
39
+ Now create a Parallizer for that service and add all of the methods you intend to call. This begins the execution of the service methods in worker threads. Then create a service proxy that uses the stored results of the method calls.
32
40
 
33
41
  ```ruby
34
42
  require 'parallizer'
35
43
 
36
44
  parallizer = Parallizer.new($search_service)
37
- parallizer.add.search_result_for_foo
38
- parallizer.add.search_result_for_bar
39
- search_service = parallizer.execute
45
+ parallizer.add.top_urls_for_foo
46
+ parallizer.add.top_urls_for_bar
47
+ search_service = parallizer.create_proxy
40
48
  ```
41
49
 
42
- Now use that service proxy in your application logic.
50
+ Now use that service proxy in your application logic. Calls to these methods will not make an HTTP request
51
+ and will not parse HTML. That was done by the parallel worker threads.
43
52
 
44
53
  ```ruby
45
- puts search_service.search_result_for_foo
46
- puts search_service.search_result_for_bar
54
+ puts search_service.top_urls_for_foo
55
+ puts search_service.top_urls_for_bar
47
56
  ```
48
57
 
49
58
  Additional calls in your application logic will not result in an additional call to the underlying service.
50
59
 
51
60
  ```ruby
52
61
  # Called twice, but no extra service call. (Be careful not to mutate the returned object!)
53
- puts search_service.search_result_for_foo
54
- puts search_service.search_result_for_foo
62
+ puts search_service.top_urls_for_foo
63
+ puts search_service.top_urls_for_foo
55
64
  ```
56
65
 
57
66
  If there are additional methods on your service that were not parallized, you can still call them.
58
67
 
59
68
  ```ruby
60
- puts search_service.search_result_for_foobar # does a Net::HTTP.get call
69
+ puts search_service.top_urls_for_foobar # makes an HTTP request and parses result
61
70
  ```
62
71
 
63
72
  ### Parallizing methods with parameters
@@ -66,11 +75,18 @@ Parallizing also works on service methods with parameters.
66
75
 
67
76
  ```ruby
68
77
  require 'net/http'
78
+ require 'nokogiri'
69
79
  require 'cgi'
70
80
 
71
81
  class SearchService
72
- def search_result(search_term)
73
- Net::HTTP.get('www.google.com', "/?q=#{CGI.escape(search_term)}")
82
+ def top_urls(search_term)
83
+ parse_search_result_for_urls(Net::HTTP.get('www.google.com', "/search?q=#{CGI.escape(search_term)}"))
84
+ end
85
+
86
+ private
87
+
88
+ def parse_search_result_for_urls(content)
89
+ Nokogiri::HTML.parse(content).search('h3.r > a').collect(&:attributes).collect{ |attrs| attrs['href'].value }
74
90
  end
75
91
  end
76
92
 
@@ -83,17 +99,17 @@ The parallel execution and proxy creation.
83
99
  require 'parallizer'
84
100
 
85
101
  parallizer = Parallizer.new($search_service)
86
- parallizer.add.search_result('foo')
87
- parallizer.add.search_result('bar')
88
- search_service = parallizer.execute
102
+ parallizer.add.top_urls('foo')
103
+ parallizer.add.top_urls('bar')
104
+ search_service = parallizer.create_proxy
89
105
  ```
90
106
 
91
107
  Using the service proxy in your application logic.
92
108
 
93
109
  ```ruby
94
- puts search_service.search_result('foo') # returns stored value
95
- puts search_service.search_result('bar') # returns stored value
96
- puts search_service.search_result('foobar') # does a Net::HTTP.get call
110
+ puts search_service.top_urls('foo') # returns stored value
111
+ puts search_service.top_urls('bar') # returns stored value
112
+ puts search_service.top_urls('foobar') # makes an HTTP request and parses result
97
113
  ```
98
114
 
99
115
 
@@ -108,24 +124,24 @@ require 'parallizer'
108
124
  parallizer = Parallizer.new(Net::HTTP)
109
125
  parallizer.add.get('www.google.com', '/?q=foo')
110
126
  parallizer.add.get('www.google.com', '/?q=bar')
111
- http_service = parallizer.execute
127
+ http_service = parallizer.create_proxy
112
128
  ```
113
129
 
114
130
  Use the service proxy.
115
131
 
116
132
  ```ruby
117
133
  # use your service proxy
118
- http_service.get('www.google.com', '/?q=foo') # returns stored value
119
- http_service.get('www.google.com', '/?q=bar') # returns stored value
120
- http_service.get('www.google.com', '/?q=foobar') # does a Net::HTTP.get call
134
+ http_service.get('www.google.com', '/search?q=foo') # returns stored value
135
+ http_service.get('www.google.com', '/search?q=bar') # returns stored value
136
+ http_service.get('www.google.com', '/search?q=foobar') # makes an HTTP request and parses result
121
137
  ```
122
138
 
123
139
 
124
140
  # Credits
125
141
 
126
- [Parallizer](https://github.com/michaelgpearce/parallizer) is maintained by [Michael Pearce](http://github.com/michaelgpearce) and is funded by [BookRenter.com](http://www.bookrenter.com "BookRenter.com").
142
+ [Parallizer](https://github.com/michaelgpearce/parallizer) is maintained by [Michael Pearce](http://github.com/michaelgpearce) and is funded by [Rafter](http://www.rafter.com "Rafter").
127
143
 
128
- ![BookRenter.com Logo](http://assets0.bookrenter.com/images/header/bookrenter_logo.gif "BookRenter.com")
144
+ ![Rafter Logo](http://rafter-logos.s3.amazonaws.com/rafter_github_logo.png "Rafter")
129
145
 
130
146
  # Copyright
131
147
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.4
1
+ 0.1.0
data/lib/parallizer.rb CHANGED
@@ -1,56 +1,60 @@
1
- require 'set'
2
1
  require 'work_queue'
3
2
  require 'parallizer/proxy'
4
3
  require 'parallizer/method_call_notifier'
5
4
 
6
5
  class Parallizer
7
- attr_accessor :calls, :client
6
+ attr_reader :calls, :call_infos, :client, :proxy
8
7
 
9
8
  def initialize(client)
10
- self.client = client
11
- self.calls = Set.new
9
+ @client = client
10
+ @call_infos = {}
12
11
  end
13
12
 
14
13
  def add
15
14
  MethodCallNotifier.new do |*args|
16
- self.calls.add(args)
15
+ add_call(*args)
17
16
  end
18
17
  end
19
18
 
20
- def add_call(method_name, *args)
21
- calls.add([method_name.to_sym, *args])
22
- end
23
-
24
- def execute
25
- Parallizer.execute_all(self).first
19
+ def calls
20
+ @call_infos.keys
26
21
  end
27
22
 
28
- def self.work_queue
29
- # TODO: share the work queue among calling threads
30
- Thread.current[:parallizer_work_queue] ||= WorkQueue.new(10)
31
- end
32
-
33
- def self.execute_all(*parallizers)
34
- parallizers_execution_results = {}
35
- parallizers.each do |parallizer|
36
- execution_results = {}
37
- parallizers_execution_results[parallizer] = execution_results
38
-
39
- parallizer.calls.each do |name_and_args|
40
- Parallizer.work_queue.enqueue_b do
41
- begin
42
- execution_results[name_and_args] = {:result => parallizer.client.send(*name_and_args)}
43
- rescue Exception => e
44
- execution_results[name_and_args] = {:exception => e}
45
- end
23
+ def add_call(method_name, *args)
24
+ raise ArgumentError, "Cannot add calls after proxy has been generated" if @proxy
25
+
26
+ method_name_and_args = [method_name.to_sym, *args]
27
+
28
+ call_info = {
29
+ :complete? => false,
30
+ :result => nil,
31
+ :exception => nil,
32
+ :condition_variable => ConditionVariable.new,
33
+ :mutex => Mutex.new
34
+ }
35
+ call_infos[method_name_and_args] = call_info
36
+
37
+ Parallizer.work_queue.enqueue_b do
38
+ call_info[:mutex].synchronize do
39
+ begin
40
+ call_info[:result] = client.send(*method_name_and_args)
41
+ rescue Exception => e
42
+ call_info[:exception] = e
43
+ ensure
44
+ call_info[:complete?] = true
45
+ call_info[:condition_variable].signal
46
46
  end
47
47
  end
48
48
  end
49
+ end
50
+
51
+ def create_proxy
52
+ raise ArgumentError, "Cannot create another proxy" if @proxy
49
53
 
50
- Parallizer.work_queue.join
51
-
52
- return parallizers_execution_results.collect do |parallizer, execution_results|
53
- Parallizer::Proxy.new(parallizer.client, execution_results)
54
- end
54
+ Parallizer::Proxy.new(client, call_infos)
55
+ end
56
+
57
+ def self.work_queue
58
+ @parallizer_work_queue ||= WorkQueue.new(10)
55
59
  end
56
60
  end
@@ -1,27 +1,38 @@
1
+ require 'thread'
1
2
  require 'set'
2
3
 
3
4
  class Parallizer
4
5
  class Proxy
5
- def initialize(client, execution_results)
6
+ def initialize(client, call_infos)
6
7
  @client = client
7
- @execution_results = execution_results
8
+ @call_infos = call_infos
8
9
  end
9
10
 
10
11
  def method_missing(name, *args, &block)
11
- if @execution_results.key?([name, *args])
12
- value = @execution_results[[name, *args]]
13
- if value[:exception]
14
- raise value[:exception]
15
- else
16
- value[:result]
12
+ value = nil
13
+
14
+ if call_info = @call_infos[[name, *args]]
15
+ call_info[:mutex].synchronize do
16
+ if !call_info[:complete?]
17
+ # not done, so lets wait for signal of completion
18
+ call_info[:condition_variable].wait(call_info[:mutex])
19
+ end
20
+ # we now have our result from the worker thread
21
+
22
+ raise call_info[:exception] if call_info[:exception]
23
+
24
+ value = call_info[:result]
17
25
  end
18
26
  else
19
- @client.send(*[name, *args], &block)
27
+ # pass through to client since not added
28
+ value = @client.send(*[name, *args], &block)
20
29
  end
30
+
31
+ value
21
32
  end
22
33
 
23
34
  def respond_to?(name, include_private = false, &block)
24
- @execution_methods ||= Set.new(@execution_results.keys.collect(&:first))
35
+ @execution_methods ||= Set.new(@call_infos.keys.collect(&:first))
25
36
 
26
37
  if @execution_methods.include?(name.to_sym)
27
38
  true
data/parallizer.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "parallizer"
8
- s.version = "0.0.4"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Michael Pearce"]
12
- s.date = "2012-07-30"
12
+ s.date = "2012-08-04"
13
13
  s.description = "Execute your service layer in parallel."
14
14
  s.email = "michael.pearce@bookrenter.com"
15
15
  s.extra_rdoc_files = [
@@ -27,10 +27,10 @@ Gem::Specification.new do |s|
27
27
  "lib/parallizer/method_call_notifier.rb",
28
28
  "lib/parallizer/proxy.rb",
29
29
  "parallizer.gemspec",
30
+ "test/helper.rb",
30
31
  "test/parallizer/method_call_notifier_test.rb",
31
32
  "test/parallizer/proxy_test.rb",
32
- "test/parallizer_test.rb",
33
- "test/test_helper.rb"
33
+ "test/parallizer_test.rb"
34
34
  ]
35
35
  s.homepage = "http://github.com/michaelgpearce/parallizer"
36
36
  s.licenses = ["MIT"]
File without changes
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require 'helper'
2
2
 
3
3
  class Parallizer::MethodCallNotifierTest < Test::Unit::TestCase
4
4
  context ".method_missing" do
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require 'helper'
2
2
 
3
3
  class Parallizer::ProxyTest < Test::Unit::TestCase
4
4
  DEFAULT_RETURN_VALUE = "return value"
@@ -12,53 +12,82 @@ class Parallizer::ProxyTest < Test::Unit::TestCase
12
12
  context ".method_missing" do
13
13
  setup do
14
14
  @client = TestObject.new
15
+ @call_key = []
16
+ @call_info = {:result => nil, :exception => nil, :complete? => true,
17
+ :condition_variable => ConditionVariable.new, :mutex => Mutex.new }
18
+ end
19
+
20
+ execute do
21
+ call_infos = { @call_key => @call_info }
22
+ proxy = Parallizer::Proxy.new(@client, call_infos)
23
+ proxy.send(*@call_key) rescue $!
15
24
  end
16
25
 
17
26
  context "with method that exists on client" do
18
- context "with method and arg in execute results" do
27
+ context "with method and arg call info" do
19
28
  setup do
20
- @method_result = "some value"
21
- @method_arg = "some arg"
22
- @execution_results = {[:a_method, @method_arg] => {:result => @method_result}}
29
+ @call_key += [:a_method, "some value"]
23
30
  end
24
-
25
- should "return value from execution_results" do
26
- assert_equal @method_result, Parallizer::Proxy.new(@client, @execution_results).a_method(@method_arg)
31
+
32
+ context "with not complete?" do
33
+ setup do
34
+ @call_info[:complete?] = false
35
+ @call_info[:condition_variable].expects(:wait).with(@call_info[:mutex])
36
+ @call_info[:result] = 'this is a value'
37
+ end
38
+
39
+ should do
40
+ assert_equal @call_info[:result], @execute_result
41
+ end
27
42
  end
28
-
29
- context "with exception in execute results" do
43
+
44
+ context "with complete? call info" do
30
45
  setup do
31
- @execution_results = {[:a_method, @method_arg] => {:exception => Exception.new("an exception")}}
46
+ @call_info[:complete?] = true
32
47
  end
33
48
 
34
- should "raise exception" do
35
- assert_raises Exception, "an exception" do
36
- Parallizer::Proxy.new(@client, @execution_results).a_method(@method_arg)
49
+ context "with an exception" do
50
+ setup do
51
+ @call_info[:exception] = StandardError.new('An Exception')
52
+ end
53
+
54
+ should "raise exception" do
55
+ assert_equal @call_info[:exception], @execute_result
56
+ end
57
+ end
58
+
59
+ context "with a result" do
60
+ setup do
61
+ @call_info[:result] = "a result"
62
+ end
63
+
64
+ should "return result" do
65
+ assert_equal @call_info[:result], @execute_result
37
66
  end
38
67
  end
39
68
  end
40
69
  end
41
70
 
42
- context "with method and arg not in execute result" do
71
+ context "with no method and arg not in execute result" do
43
72
  setup do
44
- @execution_results = {[:a_method, :unknown] => "unknown"}
73
+ @call_key += [:a_method, "some parameter"]
74
+ @call_info = nil
45
75
  end
46
-
76
+
47
77
  should "return value from client object" do
48
- assert_equal DEFAULT_RETURN_VALUE, Parallizer::Proxy.new(@client, @execution_results).a_method(:not_unknown)
78
+ assert_equal DEFAULT_RETURN_VALUE, @execute_result
49
79
  end
50
80
  end
51
81
  end
52
82
 
53
83
  context "with method that does not exist on client" do
54
84
  setup do
55
- @execution_results = {[:a_method, @method_arg] => {:result => @method_result}}
85
+ @call_key += [:unknown_method, "some parameter"]
86
+ @call_info = nil
56
87
  end
57
88
 
58
89
  should "raise exception" do
59
- assert_raises NoMethodError, "an exception" do
60
- Parallizer::Proxy.new(@client, @execution_results).unknown_method()
61
- end
90
+ assert_equal NoMethodError, @execute_result.class
62
91
  end
63
92
  end
64
93
  end
@@ -66,8 +95,10 @@ class Parallizer::ProxyTest < Test::Unit::TestCase
66
95
  context ".respond_to?" do
67
96
  setup do
68
97
  client = TestObject.new
69
- execution_results = {[:a_method, 'valid argument'] => {:result => 'valid result'}}
70
- @proxy = Parallizer::Proxy.new(client, execution_results)
98
+ call_key = [:a_method, 'valid argument']
99
+ call_info = {:result => nil, :exception => nil, :complete? => true,
100
+ :condition_variable => ConditionVariable.new, :mutex => Mutex.new }
101
+ @proxy = Parallizer::Proxy.new(client, { call_key => call_info })
71
102
  end
72
103
 
73
104
  should "respond to proxy method as symbol" do
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require 'helper'
2
2
 
3
3
  class ParallizerTest < Test::Unit::TestCase
4
4
  class TestObject
@@ -43,7 +43,17 @@ class ParallizerTest < Test::Unit::TestCase
43
43
  end
44
44
 
45
45
  execute do
46
- @parallizer.add_call(@method, 'arg')
46
+ @parallizer.add_call(@method, 'arg') rescue $!
47
+ end
48
+
49
+ context "with proxy already created" do
50
+ setup do
51
+ @parallizer.instance_variable_set(:@proxy, mock('proxy'))
52
+ end
53
+
54
+ should "raise ArgumentError" do
55
+ assert_equal ArgumentError, @execute_result.class
56
+ end
47
57
  end
48
58
 
49
59
  context "with string method name added" do
@@ -67,14 +77,14 @@ class ParallizerTest < Test::Unit::TestCase
67
77
  end
68
78
  end
69
79
 
70
- context ".execute" do
80
+ context ".create_proxy" do
71
81
  setup do
72
82
  @client = TestObject.new
73
83
  @parallizer = Parallizer.new(@client)
74
84
  end
75
85
 
76
86
  execute do
77
- @proxy = @parallizer.execute
87
+ @proxy = @parallizer.create_proxy rescue $!
78
88
  end
79
89
 
80
90
  context "with existing method on client" do
@@ -90,30 +100,16 @@ class ParallizerTest < Test::Unit::TestCase
90
100
  assert_equal Thread.current, @proxy.another_method
91
101
  end
92
102
  end
93
- end
94
-
95
- context ".execute_all" do
96
- setup do
97
- @client1 = TestObject.new
98
- @parallizer1 = Parallizer.new(@client1)
99
- @parallizer1.add_call(:a_method, 'arg')
100
- @client2 = AnotherTestObject.new
101
- @parallizer2 = Parallizer.new(@client2)
102
- @parallizer2.add_call(:a_method)
103
- end
104
-
105
- execute do
106
- @proxy1, @proxy2 = Parallizer.execute_all(@parallizer1, @parallizer2)
107
- end
108
-
109
- should "execute methods with add_call in a separate thread" do
110
- assert_not_equal Thread.current, @proxy1.a_method('arg')
111
- assert_not_equal Thread.current, @proxy2.a_method
112
- end
113
-
114
- should "execute methods not added with add_call in current thread" do
115
- assert_equal Thread.current, @proxy1.another_method
116
- assert_equal Thread.current, @proxy2.another_method
103
+
104
+ context "with proxy already created" do
105
+ setup do
106
+ @parallizer.instance_variable_set(:@proxy, mock('proxy'))
107
+ end
108
+
109
+ should "raise ArgumentError" do
110
+ assert_equal ArgumentError, @execute_result.class
111
+ end
117
112
  end
113
+
118
114
  end
119
115
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parallizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-07-30 00:00:00.000000000 Z
12
+ date: 2012-08-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -109,10 +109,10 @@ files:
109
109
  - lib/parallizer/method_call_notifier.rb
110
110
  - lib/parallizer/proxy.rb
111
111
  - parallizer.gemspec
112
+ - test/helper.rb
112
113
  - test/parallizer/method_call_notifier_test.rb
113
114
  - test/parallizer/proxy_test.rb
114
115
  - test/parallizer_test.rb
115
- - test/test_helper.rb
116
116
  homepage: http://github.com/michaelgpearce/parallizer
117
117
  licenses:
118
118
  - MIT
@@ -128,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
128
128
  version: '0'
129
129
  segments:
130
130
  - 0
131
- hash: -1557478882810554134
131
+ hash: -894887093549594031
132
132
  required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  none: false
134
134
  requirements: