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 +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
|