parallizer 0.0.4 → 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/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: