carbon 2.1.0 → 2.2.1

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/CHANGELOG CHANGED
@@ -1,9 +1,26 @@
1
+ 2.2.1 / 2012-03-21
2
+
3
+ * Enhancements
4
+
5
+ * Switched from EventMachine to pure Ruby threads, a small speed sacrifice that gains us compatibilty with MRI 1.8, MRI 1.9, and JRuby. Also works better in multithreaded environments - no accidentally turning off somebody's EventMachine reactor :)
6
+
7
+ 2.2.0 / 2012-03-20
8
+
9
+ * Enhancements
10
+
11
+ * Carbon.query(os) now returns a Hash keyed by the "os", instead of just a carefully ordered Array.
12
+
13
+ * Breaking changes
14
+
15
+ * Carbon.query(os) has a different return value!
16
+
1
17
  2.1.0 / 2012-03-14
2
18
 
3
19
  * Enhancements
4
20
 
5
21
  * Simplified API down to just Carbon.query. It has three method signatures.
6
22
  * Simplified EventMachine reactor
23
+ * Limited concurrency to 16 connections at a time
7
24
 
8
25
  * Breaking changes
9
26
 
@@ -15,7 +15,6 @@ Gem::Specification.new do |s|
15
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
16
  s.require_paths = ["lib"]
17
17
 
18
- s.add_runtime_dependency 'em-http-request'
19
18
  s.add_runtime_dependency 'activesupport'
20
19
  s.add_runtime_dependency 'multi_json'
21
20
  s.add_runtime_dependency 'hashie'
@@ -66,11 +66,11 @@ module Carbon
66
66
  # @overload query(os)
67
67
  # Get multiple impact estimates for arrays and/or query-able objects concurrently.
68
68
  # @param [Array<Array, #as_impact_query>] os An array of arrays in +[emitter, params]+ format and/or objects that respond to +#as_impact_query+.
69
- # @return [Array<Hashie::Mash>] An array of +Hashie::Mash+ objects in the same order.
69
+ # @return [Hash{Object => Hashie::Mash}] A +Hash+ of +Hashie::Mash+ objects, keyed on the original query object.
70
70
  #
71
71
  # @note We make up to 16 requests concurrently (hardcoded, per the Brighter Planet Terms of Service) and it can be more than 90% faster than running queries serially!
72
72
  #
73
- # @note Using concurrency on JRuby, you may get errors like SOCKET: SET COMM INACTIVITY UNIMPLEMENTED 10 because under the hood we're using {https://github.com/igrigorik/em-http-request em-http-request}, which suffers from {https://github.com/eventmachine/eventmachine/issues/155 an issue with +pending_connect_timeout+}.
73
+ # @raise [ArgumentError] If your arguments don't match any of the method signatures.
74
74
  #
75
75
  # @example A flight taken in 2009
76
76
  # Carbon.query('Flight', :origin_airport => 'MSN', :destination_airport => 'ORD', :date => '2009-01-01', :timeframe => Timeframe.new(:year => 2009), :comply => [:tcr])
@@ -107,7 +107,7 @@ module Carbon
107
107
  # @example Flights and cars (concurrently, as query-able objects)
108
108
  # Carbon.query(MyFlight.all+MyCar.all)
109
109
  #
110
- # @example Cars month-by-month
110
+ # @example Cars month-by-month (note that you won't get MyCar objects back, you'll get Arrays back. This will be fixed soon.)
111
111
  # cars_by_month = MyCar.all.inject([]) do |memo, my_car|
112
112
  # months.each do |first_day_of_the_month|
113
113
  # my_car.as_impact_query(:date => first_day_of_the_month)
@@ -131,9 +131,12 @@ module Carbon
131
131
  future.multi!
132
132
  future
133
133
  end
134
- Future.multi(futures).map do |future|
135
- future.result
134
+ Future.multi(futures).inject({}) do |memo, future|
135
+ memo[future.object] = future.result
136
+ memo
136
137
  end
138
+ else
139
+ raise ::ArgumentError, "Didn't match any of the method signatures. If you want multiple queries, make sure to pass an unsplatted Array."
137
140
  end
138
141
  end
139
142
 
@@ -3,18 +3,19 @@ require 'net/http'
3
3
  require 'cache_method'
4
4
  require 'hashie/mash'
5
5
  require 'multi_json'
6
- require 'em-http-request'
7
6
 
8
7
  module Carbon
9
8
  # @private
10
9
  class Future
11
10
  class << self
12
11
  def wrap(query_array_or_o)
13
- if query_array_or_o.is_a?(::Array)
12
+ future = if query_array_or_o.is_a?(::Array)
14
13
  new(*query_array_or_o)
15
14
  else
16
15
  new(*query_array_or_o.as_impact_query)
17
16
  end
17
+ future.object = query_array_or_o
18
+ future
18
19
  end
19
20
 
20
21
  def single(future)
@@ -26,20 +27,12 @@ module Carbon
26
27
 
27
28
  def multi(futures)
28
29
  uniq_pending_futures = futures.uniq.select { |future| future.pending? }
29
- return futures if uniq_pending_futures.empty?
30
- pool_size = [Carbon::CONCURRENCY, uniq_pending_futures.length].min
31
- multi = ::EventMachine::MultiRequest.new
32
- pool = (0..(pool_size-1)).map { ::EventMachine::HttpRequest.new(Carbon::DOMAIN) }
33
- pool_idx = 0
34
- ::EventMachine.run do
35
- uniq_pending_futures.each do |future|
36
- multi.add future, pool[pool_idx].post(:path => "/#{future.emitter.underscore.pluralize}.json", :body => future.params)
37
- pool_idx = (pool_idx + 1) % pool_size
30
+ while (set = uniq_pending_futures.pop(Carbon::CONCURRENCY)).any?
31
+ ts = set.map do |future|
32
+ ::Thread.new { single future }
38
33
  end
39
- multi.callback do
40
- multi.responses[:callback].each { |future, http| future.finalize http.response_header.status, http.response }
41
- multi.responses[:errback].each { |future, http| future.finalize http.response_header.status }
42
- ::EventMachine.stop
34
+ ts.each do |t|
35
+ t.join
43
36
  end
44
37
  end
45
38
  futures
@@ -49,6 +42,8 @@ module Carbon
49
42
  attr_reader :emitter
50
43
  attr_reader :params
51
44
 
45
+ attr_accessor :object
46
+
52
47
  def initialize(emitter, params = {})
53
48
  @result = nil
54
49
  @emitter = emitter
@@ -1,3 +1,3 @@
1
1
  module Carbon
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.1"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require File.expand_path("../helper", __FILE__)
2
2
 
3
+ Thread.abort_on_exception = true
3
4
  Carbon.key = 'carbon_test'
4
5
 
5
6
  class MyNissan
@@ -77,9 +78,15 @@ describe Carbon do
77
78
  result.errors.first.must_equal 'Good job'
78
79
  end
79
80
  end
81
+ it "raises ArgumentError if args are bad" do
82
+ lambda {
83
+ Carbon.query(['Flight'])
84
+ }.must_raise ArgumentError
85
+ end
80
86
  end
81
87
  describe '(in parallel)' do
82
88
  before do
89
+ flunk if ENV['SKIP_MULTI'] == 'true'
83
90
  @queries = []
84
91
  @queries << ['Flight', {:origin_airport => 'LAX', :destination_airport => 'SFO', :segments_per_trip => 1, :trips => 1}]
85
92
  @queries << ['Flight', {:origin_airport => 'MSN', :destination_airport => 'ORD', :segments_per_trip => 1, :trips => 1}]
@@ -98,34 +105,50 @@ describe Carbon do
98
105
  @queries << ['Monkey', {:bananas => '3'}]
99
106
  @queries = @queries.sort_by { rand }
100
107
  end
108
+ it "is easy to use" do
109
+ flight = ['Flight']
110
+ rail_trip = ['RailTrip']
111
+ results = Carbon.query([flight, rail_trip])
112
+ results[flight].decisions.must_equal Carbon.query('Flight').decisions
113
+ results[rail_trip].decisions.must_equal Carbon.query('RailTrip').decisions
114
+ end
101
115
  it "doesn't hang up on 0 queries" do
102
- Timeout.timeout(0.5) { Carbon.query([]) }.must_equal []
116
+ Timeout.timeout(0.5) { Carbon.query([]) }.must_equal(Hash.new)
103
117
  end
104
118
  it "can be used on objects that respond to #as_impact_query" do
105
- Carbon.query([MyNissanAltima.new(2001), MyNissanAltima.new(2006)]).map(&:decisions).must_equal Carbon.query([MyNissanAltima.new(2001).as_impact_query, MyNissanAltima.new(2006).as_impact_query]).map(&:decisions)
119
+ Carbon.query([MyNissanAltima.new(2001), MyNissanAltima.new(2006)]).values.map(&:decisions).map(&:carbon).map(&:object).map(&:value).must_equal Carbon.query([MyNissanAltima.new(2001).as_impact_query, MyNissanAltima.new(2006).as_impact_query]).values.map(&:decisions).map(&:carbon).map(&:object).map(&:value)
106
120
  end
107
121
  it "runs multiple queries at once" do
108
- reference_results = @queries.map do |query|
109
- Carbon.query(*query)
122
+ reference_results = @queries.inject({}) do |memo, query|
123
+ memo[query] = Carbon.query(*query)
124
+ memo
110
125
  end
111
- flush_cache! # important!
112
- multi_results = Carbon.query(@queries)
113
- error_count = 0
114
- multi_results.each do |result|
115
- if result.success
116
- result.decisions.carbon.object.value.must_be :>, 0
117
- result.decisions.carbon.object.value.must_be :<, 10_000
118
- else
119
- error_count += 1
126
+ ts = []
127
+ 3.times do
128
+ ts << Thread.new do
129
+ flush_cache! # important!
130
+ multi_results = Carbon.query(@queries)
131
+ error_count = 0
132
+ multi_results.each do |query, result|
133
+ if result.success
134
+ result.decisions.carbon.object.value.must_be :>, 0
135
+ result.decisions.carbon.object.value.must_be :<, 10_000
136
+ else
137
+ error_count += 1
138
+ end
139
+ end
140
+ error_count.must_equal 3
141
+ reference_results.each do |query, reference_result|
142
+ if reference_result.success
143
+ multi_results[query].decisions.must_equal reference_result.decisions
144
+ else
145
+ multi_results[query].must_equal reference_result
146
+ end
147
+ end
120
148
  end
121
149
  end
122
- error_count.must_equal 3
123
- reference_results.each_with_index do |reference_result, idx|
124
- if reference_result.success
125
- multi_results[idx].decisions.must_equal reference_result.decisions
126
- else
127
- multi_results[idx].must_equal reference_result
128
- end
150
+ ts.each do |t|
151
+ t.join
129
152
  end
130
153
  end
131
154
  it "is faster than single threaded" do
@@ -153,17 +176,18 @@ describe Carbon do
153
176
  $stderr.puts " Cached multi-threaded was #{((multi_threaded_time - cached_multi_threaded_time) / multi_threaded_time * 100).round}% faster than uncached multi-threaded"
154
177
  end
155
178
  it "safely uniq's and caches queries" do
156
- reference_results = @queries.map do |query|
157
- Carbon.query(*query)
179
+ reference_results = @queries.inject({}) do |memo, query|
180
+ memo[query] = Carbon.query(*query)
181
+ memo
158
182
  end
159
183
  flush_cache! # important!
160
184
  3.times do
161
185
  multi_results = Carbon.query(@queries)
162
- reference_results.each_with_index do |reference_result, idx|
186
+ reference_results.each do |query, reference_result|
163
187
  if reference_result.success
164
- multi_results[idx].decisions.must_equal reference_result.decisions
188
+ multi_results[query].decisions.must_equal reference_result.decisions
165
189
  else
166
- multi_results[idx].must_equal reference_result
190
+ multi_results[query].must_equal reference_result
167
191
  end
168
192
  end
169
193
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carbon
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-15 00:00:00.000000000 Z
12
+ date: 2012-03-21 00:00:00.000000000 Z
13
13
  dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: em-http-request
16
- requirement: &2164986440 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '0'
22
- type: :runtime
23
- prerelease: false
24
- version_requirements: *2164986440
25
14
  - !ruby/object:Gem::Dependency
26
15
  name: activesupport
27
- requirement: &2165006220 !ruby/object:Gem::Requirement
16
+ requirement: &2169272660 !ruby/object:Gem::Requirement
28
17
  none: false
29
18
  requirements:
30
19
  - - ! '>='
@@ -32,10 +21,10 @@ dependencies:
32
21
  version: '0'
33
22
  type: :runtime
34
23
  prerelease: false
35
- version_requirements: *2165006220
24
+ version_requirements: *2169272660
36
25
  - !ruby/object:Gem::Dependency
37
26
  name: multi_json
38
- requirement: &2165005800 !ruby/object:Gem::Requirement
27
+ requirement: &2169272200 !ruby/object:Gem::Requirement
39
28
  none: false
40
29
  requirements:
41
30
  - - ! '>='
@@ -43,10 +32,10 @@ dependencies:
43
32
  version: '0'
44
33
  type: :runtime
45
34
  prerelease: false
46
- version_requirements: *2165005800
35
+ version_requirements: *2169272200
47
36
  - !ruby/object:Gem::Dependency
48
37
  name: hashie
49
- requirement: &2165005380 !ruby/object:Gem::Requirement
38
+ requirement: &2169271780 !ruby/object:Gem::Requirement
50
39
  none: false
51
40
  requirements:
52
41
  - - ! '>='
@@ -54,10 +43,10 @@ dependencies:
54
43
  version: '0'
55
44
  type: :runtime
56
45
  prerelease: false
57
- version_requirements: *2165005380
46
+ version_requirements: *2169271780
58
47
  - !ruby/object:Gem::Dependency
59
48
  name: cache_method
60
- requirement: &2165004960 !ruby/object:Gem::Requirement
49
+ requirement: &2169271220 !ruby/object:Gem::Requirement
61
50
  none: false
62
51
  requirements:
63
52
  - - ! '>='
@@ -65,10 +54,10 @@ dependencies:
65
54
  version: '0'
66
55
  type: :runtime
67
56
  prerelease: false
68
- version_requirements: *2165004960
57
+ version_requirements: *2169271220
69
58
  - !ruby/object:Gem::Dependency
70
59
  name: bombshell
71
- requirement: &2165004500 !ruby/object:Gem::Requirement
60
+ requirement: &2169270600 !ruby/object:Gem::Requirement
72
61
  none: false
73
62
  requirements:
74
63
  - - ! '>='
@@ -76,10 +65,10 @@ dependencies:
76
65
  version: '0'
77
66
  type: :runtime
78
67
  prerelease: false
79
- version_requirements: *2165004500
68
+ version_requirements: *2169270600
80
69
  - !ruby/object:Gem::Dependency
81
70
  name: conversions
82
- requirement: &2165004060 !ruby/object:Gem::Requirement
71
+ requirement: &2169269920 !ruby/object:Gem::Requirement
83
72
  none: false
84
73
  requirements:
85
74
  - - ! '>='
@@ -87,10 +76,10 @@ dependencies:
87
76
  version: '0'
88
77
  type: :runtime
89
78
  prerelease: false
90
- version_requirements: *2165004060
79
+ version_requirements: *2169269920
91
80
  - !ruby/object:Gem::Dependency
92
81
  name: brighter_planet_metadata
93
- requirement: &2165003560 !ruby/object:Gem::Requirement
82
+ requirement: &2169269500 !ruby/object:Gem::Requirement
94
83
  none: false
95
84
  requirements:
96
85
  - - ! '>='
@@ -98,7 +87,7 @@ dependencies:
98
87
  version: '0'
99
88
  type: :runtime
100
89
  prerelease: false
101
- version_requirements: *2165003560
90
+ version_requirements: *2169269500
102
91
  description: Brighter Planet API client for Ruby
103
92
  email:
104
93
  - seamus@abshere.net
@@ -118,7 +107,6 @@ files:
118
107
  - bin/carbon
119
108
  - carbon.gemspec
120
109
  - developer/MULTI.markdown
121
- - developer/REDUCE_HTTP_CONNECTIONS.markdown
122
110
  - developer/avro_helper.rb
123
111
  - developer/cm1_avro.rb
124
112
  - features/shell.feature
@@ -1,46 +0,0 @@
1
- One way to reduce the number of connections by a constant... but it makes it slower (because requests are serialized) and less reliable (because there is a 30s heroku limit)
2
-
3
- def self.multi(queries)
4
- unsorted = {}
5
- pool_size = (queries.length.to_f / 3).ceil
6
- $stderr.puts "Starting #{pool_size} workers"
7
- ::EventMachine.run do
8
- multi = ::EventMachine::MultiRequest.new
9
- pool = 0.upto(pool_size).map do
10
- ::EventMachine::HttpRequest.new("http://#{domain}")
11
- end
12
- pool_idx = 0
13
- queries.each_with_index do |(emitter, params), query_idx|
14
- params ||= {}
15
- multi.add query_idx, pool[pool_idx].post(:path => "/#{emitter.underscore.pluralize}.json", :body => params, :keepalive => true)
16
- pool_idx = (pool_idx + 1) % pool_size
17
- end
18
- multi.callback do
19
- multi.responses[:callback].each do |query_idx, http|
20
- result = ::Hashie::Mash.new
21
- result.status = http.response_header.status
22
- if (200..299).include?(result.status)
23
- result.success = true
24
- result.merge! ::MultiJson.decode(http.response)
25
- else
26
- result.success = false
27
- result.errors = [http.response]
28
- end
29
- unsorted[query_idx] = result
30
- end
31
- multi.responses[:errback].each do |query_idx, http|
32
- result = ::Hashie::Mash.new
33
- result.status = http.response_header.status
34
- result.success = false
35
- result.errors = ['Timeout or other network error.']
36
- unsorted[query_idx] = result
37
- end
38
- ::EventMachine.stop
39
- end
40
- end
41
- unsorted.sort_by do |query_idx, _|
42
- query_idx
43
- end.map do |_, result|
44
- result
45
- end
46
- end