agent-q 0.0.1r2

Sign up to get free protection for your applications and to get access to all the features.
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ /coverage
6
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+
4
+ group :test do
5
+ gem 'simplecov', '>= 0.4.0', :require => false
6
+ gem 'rspec'
7
+ end
8
+ # Specify your gem's dependencies in q.gemspec
9
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/lib/q.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "q/version"
2
+
3
+ module Q
4
+ # Your code goes here...
5
+ end
data/lib/q/array.rb ADDED
@@ -0,0 +1,7 @@
1
+ class Array
2
+ def symbolize_keys!
3
+ map do |item|
4
+ item.symbolize_keys! if item.respond_to? :symbolize_keys!
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ class BinaryString < String
2
+ def self.create from_string
3
+ from_string.force_encoding "BINARY"
4
+ from_string
5
+ end
6
+ end
@@ -0,0 +1,20 @@
1
+ module Collector
2
+ include Enumerable
3
+
4
+ def [] key
5
+ collected_items[key] ||= fetch_item key
6
+ end
7
+ def keys
8
+ @keys ||= fetch_keys
9
+ end
10
+ def each
11
+ keys.each do |key|
12
+ yield self[key]
13
+ end
14
+ end
15
+ private
16
+ def collected_items
17
+ @collected_items ||= {}
18
+ end
19
+
20
+ end
@@ -0,0 +1,29 @@
1
+ module Q
2
+ module Dispatcher
3
+ class AdapterNotFoundError < StandardError; end
4
+ class AdapterNotSpecifiedError < StandardError; end
5
+
6
+ def require_pattern pattern
7
+ @require_pattern = pattern
8
+ end
9
+ def constant_suffix suffix
10
+ @constant_suffix = suffix
11
+ end
12
+ def dispatchables
13
+ @dispatchables ||= {}
14
+ end
15
+ private :dispatchables
16
+
17
+ def [] key
18
+ return dispatchables[key] unless dispatchables[key].nil?
19
+ begin
20
+ require(@require_pattern % key.to_s)
21
+ dispatchables[key] = const_get "#{key.to_s.capitalize}#{@constant_suffix}"
22
+ rescue LoadError
23
+ m = "Unable to find #{key} adapter, make sure it is in your load path"
24
+ raise AdapterNotFoundError, m
25
+ end
26
+ end
27
+
28
+ end
29
+ end
data/lib/q/hash.rb ADDED
@@ -0,0 +1,12 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ keys.each do |key|
4
+ value = delete key
5
+ if value.is_a? Hash
6
+ value.symbolize_keys!
7
+ end
8
+ self[(key.to_sym rescue key) || key] = value
9
+ end
10
+ self
11
+ end
12
+ end
@@ -0,0 +1,97 @@
1
+ require 'monitor'
2
+
3
+ class ResourcePool
4
+ class ResourcePoolTimeoutError < StandardError; end
5
+
6
+ def initialize config
7
+ extend MonitorMixin
8
+ @config = config
9
+ end
10
+
11
+ def with_resource
12
+ result = yield resource
13
+ checkin
14
+ result
15
+ end
16
+
17
+ def create_resource
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def config
22
+ @config ||= {}
23
+ end
24
+
25
+ private
26
+ def queue
27
+ @queue ||= new_cond
28
+ end
29
+ def resources
30
+ @resources ||= []
31
+ end
32
+ def checked_out
33
+ @checked_out ||= {}
34
+ end
35
+ def timeout
36
+ config[:timeout] || 5
37
+ end
38
+ def size
39
+ config[:pool] || 5
40
+ end
41
+
42
+ def resource
43
+ checked_out[Thread.current.object_id] ||= checkout
44
+ end
45
+
46
+ def checkin(thread_id=Thread.current.object_id)
47
+ synchronize do
48
+ checked_out.delete(thread_id)
49
+ queue.signal
50
+ end
51
+ end
52
+
53
+ def find_resource
54
+ if resource_available?
55
+ find_existing_resource
56
+ elsif can_create_new?
57
+ add_new_resource
58
+ end
59
+ end
60
+
61
+ def clear_stale_resources!
62
+ #find all currenly live threads and check in their corresponding connections
63
+ alive = Thread.list.find_all { |t| t.alive? }.map { |thread| thread.object_id }
64
+ dead = checked_out.keys - alive
65
+ dead.each { |t| checkin t }
66
+ end
67
+
68
+ def resource_available?
69
+ checked_out.keys.size < resources.size
70
+ end
71
+
72
+ def can_create_new?
73
+ resources.size < size
74
+ end
75
+
76
+ def checkout
77
+ #Checkout an available connection or create a new one
78
+ synchronize do
79
+ resource = find_resource
80
+ return resource unless resource.nil?
81
+ queue.wait timeout
82
+ clear_stale_resources!
83
+ raise ResourcePoolTimeoutError unless can_create_new? or resource_available?
84
+ end
85
+ checkout
86
+ end
87
+
88
+ def find_existing_resource
89
+ (resources - checked_out.values).first
90
+ end
91
+
92
+ def add_new_resource
93
+ resources << r = create_resource; r
94
+ end
95
+
96
+ end
97
+
data/lib/q/string.rb ADDED
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_pascal
3
+ split(/_/).map { |string| string.capitalize }.join
4
+ end
5
+ end
data/lib/q/symbol.rb ADDED
@@ -0,0 +1,5 @@
1
+ class Symbol
2
+ def to_pascal
3
+ to_s.to_pascal
4
+ end
5
+ end
data/lib/q/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Q
2
+ VERSION = "0.0.1r2"
3
+ end
data/q.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "q/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "agent-q"
7
+ s.version = Q::VERSION
8
+ s.authors = ["Jacob Morris"]
9
+ s.email = ["jacob.s.morris@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Q - He's the guy with all the gadgets}
12
+ s.description = %q{Supplies you with stuff you need, exactly when you need it.}
13
+
14
+ s.rubyforge_project = "q"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'helper'
2
+ require 'q/array'
3
+
4
+ describe Array do
5
+ describe '#symbolize_keys!' do
6
+ it 'runs symbolize_keys! on hash elements' do
7
+ wrapped_hash = [{'superman' => 'is awesome'}].symbolize_keys!
8
+ wrapped_hash[0][:superman].should == 'is awesome'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'helper'
2
+ require 'q/binary_string'
3
+
4
+ describe BinaryString do
5
+ describe 'create' do
6
+ it 'creates a binary string from an existing string' do
7
+ b = BinaryString.create "from a string"
8
+ b.encoding.should == Encoding::ASCII_8BIT
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,69 @@
1
+ require 'helper'
2
+ require 'q/collector'
3
+
4
+ class FakeCollector
5
+ include Collector
6
+ def fetch_keys
7
+ [:hello, :world]
8
+ end
9
+ def fetch_item key
10
+ "hello from key -> #{key.to_s}"
11
+ end
12
+ end
13
+
14
+ class ItemProvider
15
+ def self.next_item
16
+
17
+ end
18
+ end
19
+
20
+ describe Collector do
21
+ before :each do
22
+ @collector = FakeCollector.new
23
+ end
24
+ it 'includes enumerable' do
25
+ @collector.class.included_modules.should include(Enumerable)
26
+ end
27
+ it 'defines :each' do
28
+ @collector.should respond_to(:each)
29
+ end
30
+ it 'does not define <=>' do
31
+ @collector.should_not respond_to(:<)
32
+ @collector.should_not respond_to('=')
33
+ @collector.should_not respond_to(:>)
34
+ end
35
+ describe '#keys' do
36
+ it 'calls :fetch_keys to retrieve a list of keys that correspond to collectible objects' do
37
+ @collector.should_receive :fetch_keys
38
+ @collector.keys
39
+ end
40
+ it 'caches the responce from :fetch_keys' do
41
+ keys = @collector.keys
42
+ keys.should be @collector.keys
43
+ end
44
+ end
45
+
46
+ describe '#[]' do
47
+ it 'calls #fetch_item to retreive an item' do
48
+ @collector.should_receive(:fetch_item).with(:key)
49
+ @collector[:key]
50
+ end
51
+ it 'caches the respond from #fetch_item' do
52
+ @collector[:key].should be(@collector[:key])
53
+ end
54
+ it 'fetches items that have not been cached' do
55
+ @collector[:key].should == "hello from key -> key"
56
+ @collector[:second_key].should == 'hello from key -> second_key'
57
+ end
58
+ end
59
+
60
+ describe '#each' do
61
+ it 'fetches items for all the keys and yeilds them to the block' do
62
+ @collector.keys.length.should > 0
63
+ @collector.each do |item|
64
+ item.should_not be_nil
65
+ end
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,39 @@
1
+ require 'helper'
2
+ require 'q/dispatcher'
3
+
4
+
5
+
6
+ module Q
7
+ module FakeDispatcher
8
+ extend Dispatcher
9
+ end
10
+
11
+ describe Dispatcher do
12
+ it 'responds to require_pattern' do
13
+ FakeDispatcher.should respond_to(:require_pattern)
14
+ end
15
+ it 'responds to constant_suffix' do
16
+ FakeDispatcher.should respond_to(:constant_suffix)
17
+ end
18
+ it 'sets the require pattern variable' do
19
+ FakeDispatcher.require_pattern "support/%s_adapter"
20
+ FakeDispatcher.instance_variable_get(:@require_pattern).should == "support/%s_adapter"
21
+ end
22
+ it 'sets the constant suffix variable' do
23
+ FakeDispatcher.constant_suffix "Adapter"
24
+ FakeDispatcher.instance_variable_get(:@constant_suffix).should == "Adapter"
25
+ end
26
+
27
+ describe '#[]' do
28
+ it 'retrieves a constant for the specified adapter' do
29
+ ->{ FakeAdapter }.should raise_error(NameError)
30
+ FakeDispatcher[:fake].should be(FakeAdapter)
31
+ end
32
+
33
+ it 'raises AdapterNotFoundError when the specified adapter does not exist' do
34
+ ->{ FakeDispatcher[:batman] }.should raise_error(Dispatcher::AdapterNotFoundError)
35
+ end
36
+ end
37
+
38
+ end
39
+ end
data/spec/hash_spec.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'helper'
2
+ require 'q/hash'
3
+
4
+ describe Hash do
5
+ describe '#symbolize_keys!' do
6
+ def hash
7
+ @hash ||= {
8
+ 'superheros' => {
9
+ 'batman' => {
10
+ 'whiny' => true,
11
+ 'actual_powers' => nil
12
+ },
13
+ 'superman' => {
14
+ 'whiny' => 'so absolutely false',
15
+ 'actual_powers' => [
16
+ 'leaps tall buildings in single bound',
17
+ 'faster than speeding bullet'
18
+ ]
19
+ }
20
+ }
21
+ }
22
+ end
23
+ it 'symbolizes keys' do
24
+ hash.symbolize_keys!
25
+ hash.keys.should include(:superheros)
26
+ end
27
+ it 'symbolizes keys recursively' do
28
+ hash.symbolize_keys!
29
+ hash[:superheros].keys.should include(:superman)
30
+ hash[:superheros].keys.should include(:batman)
31
+ hash[:superheros][:batman].keys.should include(:whiny)
32
+ hash[:superheros][:superman].keys.should include(:actual_powers)
33
+ end
34
+ end
35
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ require 'rspec'
@@ -0,0 +1,119 @@
1
+ require 'helper.rb'
2
+ require 'q/resource_pool'
3
+
4
+ class StringPool < ResourcePool
5
+ def create_resource
6
+ "resource ##{@resources.size}"
7
+ end
8
+ end
9
+
10
+ module Arc
11
+ describe ResourcePool do
12
+
13
+ def checked_out(pool)
14
+ pool.instance_variable_get(:@checked_out)
15
+ end
16
+
17
+ def thread_resources(pool, count=4)
18
+ threads = []
19
+ count.times do |i|
20
+ threads << Thread.start do
21
+ resource = pool.send :resource
22
+ resource.should be_a(String)
23
+ Thread.current[:resource] = resource
24
+ end
25
+ end
26
+ threads.each {|t| t.join }
27
+ end
28
+
29
+ describe '#resource' do
30
+
31
+ it "throws NotImplemented when running create_resource, which should be overridden" do
32
+ p = ResourcePool.new nil
33
+ ->{ p.create_resource }.should raise_error(NotImplementedError)
34
+ end
35
+ it 'creates a new object for each running thread' do
36
+ pool = StringPool.new :pool => 5, :timeout => 5
37
+ threads = thread_resources pool, 4
38
+ checked_out(pool).keys.size.should === 4
39
+ while t = threads.pop do
40
+ resources = checked_out(pool)
41
+ #verify connection is associated to the appropriate thread
42
+ t[:resource].should === resources.delete(t.object_id)
43
+ #make sure the array isn't just holding duplicate objects
44
+ resources.values.should_not include(t[:resource])
45
+ end
46
+ end
47
+
48
+ it 'clears resources from expired threads' do
49
+ pool = StringPool.new :pool => 5, :timeout => 0.1
50
+ threads = thread_resources(pool, 5)
51
+ checked_out(pool).keys.size.should == 5
52
+ c = pool.with_resource do |resource|
53
+ checked_out(pool).keys.size.should === 1
54
+ checked_out(pool)[Thread.current.object_id].should === resource
55
+ end
56
+ end
57
+
58
+ it 'raises error when no resources are available after timeout' do
59
+ pool = StringPool.new :pool => 1, :timeout => 0.1
60
+ pool.with_resource do |resource|
61
+ checked_out(pool).keys.size.should === 1
62
+ Thread.start do
63
+ ->{ pool.with_resource }.should raise_error ResourcePool::ResourcePoolTimeoutError
64
+ end.join
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ describe '#checkin' do
71
+ it 'makes a connection available for use by another thread' do
72
+ pool = StringPool.new :pool => 1, :timeout => 0.1
73
+ resource = pool.with_resource do |resource|
74
+ checked_out(pool).keys.size.should === 1
75
+ Thread.start do
76
+ ->{ pool.with_resource }.should raise_error ResourcePool::ResourcePoolTimeoutError
77
+ end.join
78
+ resource
79
+ end
80
+ final = Thread.start do
81
+ Thread.current[:resource] = pool.with_resource { |resource| resource }
82
+ end.join
83
+ final[:resource].should === resource
84
+ end
85
+
86
+ it 'uses the current thread id when no argument is passed' do
87
+ pool = StringPool.new :pool => 5, :timeout => 5
88
+ c = pool.with_resource do |resource|
89
+ resource
90
+ end
91
+ pool.with_resource do |resource|
92
+ resource.should be(c)
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ describe '#with_resource' do
99
+ it 'uses a resource then checks it back in' do
100
+ pool = StringPool.new :pool => 5, :timeout => 5
101
+ pool.with_resource do |resource|
102
+ resource.should be_a(String)
103
+ checked_out(pool).keys.size.should == 1
104
+ end
105
+ checked_out(pool).keys.size.should == 0
106
+ end
107
+ it 'returns the expected value' do
108
+ pool = StringPool.new :pool => 5, :timeout => 5
109
+ string = pool.with_resource do |resource|
110
+ resource.should be_a(String)
111
+ checked_out(pool).keys.size.should == 1
112
+ "this is the value"
113
+ end
114
+ string.should == "this is the value"
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,2 @@
1
+ class FakeAdapter
2
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: agent-q
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1r2
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Jacob Morris
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-21 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Supplies you with stuff you need, exactly when you need it.
15
+ email:
16
+ - jacob.s.morris@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .DS_Store
22
+ - .gitignore
23
+ - Gemfile
24
+ - Rakefile
25
+ - lib/q.rb
26
+ - lib/q/array.rb
27
+ - lib/q/binary_string.rb
28
+ - lib/q/collector.rb
29
+ - lib/q/dispatcher.rb
30
+ - lib/q/hash.rb
31
+ - lib/q/resource_pool.rb
32
+ - lib/q/string.rb
33
+ - lib/q/symbol.rb
34
+ - lib/q/version.rb
35
+ - q.gemspec
36
+ - spec/array_spec.rb
37
+ - spec/binary_string_spec.rb
38
+ - spec/collector_spec.rb
39
+ - spec/dispatcher_spec.rb
40
+ - spec/hash_spec.rb
41
+ - spec/helper.rb
42
+ - spec/resource_pool_spec.rb
43
+ - spec/support/fake_adapter.rb
44
+ homepage: ''
45
+ licenses: []
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>'
60
+ - !ruby/object:Gem::Version
61
+ version: 1.3.1
62
+ requirements: []
63
+ rubyforge_project: q
64
+ rubygems_version: 1.8.10
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Q - He's the guy with all the gadgets
68
+ test_files:
69
+ - spec/array_spec.rb
70
+ - spec/binary_string_spec.rb
71
+ - spec/collector_spec.rb
72
+ - spec/dispatcher_spec.rb
73
+ - spec/hash_spec.rb
74
+ - spec/helper.rb
75
+ - spec/resource_pool_spec.rb
76
+ - spec/support/fake_adapter.rb