agent-q 0.0.1r2
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/.DS_Store +0 -0
- data/.gitignore +6 -0
- data/Gemfile +9 -0
- data/Rakefile +1 -0
- data/lib/q.rb +5 -0
- data/lib/q/array.rb +7 -0
- data/lib/q/binary_string.rb +6 -0
- data/lib/q/collector.rb +20 -0
- data/lib/q/dispatcher.rb +29 -0
- data/lib/q/hash.rb +12 -0
- data/lib/q/resource_pool.rb +97 -0
- data/lib/q/string.rb +5 -0
- data/lib/q/symbol.rb +5 -0
- data/lib/q/version.rb +3 -0
- data/q.gemspec +20 -0
- data/spec/array_spec.rb +11 -0
- data/spec/binary_string_spec.rb +11 -0
- data/spec/collector_spec.rb +69 -0
- data/spec/dispatcher_spec.rb +39 -0
- data/spec/hash_spec.rb +35 -0
- data/spec/helper.rb +3 -0
- data/spec/resource_pool_spec.rb +119 -0
- data/spec/support/fake_adapter.rb +2 -0
- metadata +76 -0
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/lib/q.rb
ADDED
data/lib/q/array.rb
ADDED
data/lib/q/collector.rb
ADDED
@@ -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
|
data/lib/q/dispatcher.rb
ADDED
@@ -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,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
data/lib/q/symbol.rb
ADDED
data/lib/q/version.rb
ADDED
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
|
data/spec/array_spec.rb
ADDED
@@ -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,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
|
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
|