socketpool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.2"
12
+ gem "rcov", ">= 0"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ git (1.2.5)
6
+ jeweler (1.5.2)
7
+ bundler (~> 1.0.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rake (0.8.7)
11
+ rcov (0.9.9)
12
+ rspec (2.3.0)
13
+ rspec-core (~> 2.3.0)
14
+ rspec-expectations (~> 2.3.0)
15
+ rspec-mocks (~> 2.3.0)
16
+ rspec-core (2.3.1)
17
+ rspec-expectations (2.3.0)
18
+ diff-lcs (~> 1.1.2)
19
+ rspec-mocks (2.3.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ bundler (~> 1.0.0)
26
+ jeweler (~> 1.5.2)
27
+ rcov
28
+ rspec (~> 2.3.0)
data/LICENSE.txt ADDED
@@ -0,0 +1,12 @@
1
+
2
+ Licensed under the Apache License, Version 2.0 (the "License");
3
+ you may not use this file except in compliance with the License.
4
+ You may obtain a copy of the License at
5
+
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+
8
+ Unless required by applicable law or agreed to in writing, software
9
+ distributed under the License is distributed on an "AS IS" BASIS,
10
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ See the License for the specific language governing permissions and
12
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,44 @@
1
+ = socketpool
2
+
3
+ Class for managing a pool of sockets
4
+
5
+ == Using socketpool
6
+
7
+ Install:
8
+
9
+ gem install socketpool
10
+
11
+ Initialize a socket pool:
12
+
13
+ pool = SocketPool.new("127.0.0.1", "11211") # => a socket pool with a max size of 2, connected via tcp to 127.0.0.1:11211
14
+ pool = SocketPool.new("127.0.0.1", "11211", :size => 10) # => a socket pool with a max size of 10, connected via tcp to 127.0.0.1:11211
15
+ pool = SocketPool.new("127.0.0.1", "11211", :size => 10, :type => :udp) # => a socket pool with a max size of 10, connected via udp to 127.0.0.1:11211
16
+
17
+ s = pool.checkout #=> gets socket from pool
18
+ pool.checkin(s) #=> puts socket back in pool
19
+
20
+ Initialization Options:
21
+ :size #=> determines the max size of the socket pool (number of sockets that can eventually be in the pool)
22
+ :type #=> the "type" of socket in the pool: must be one of [:udp, :udp6, :tcp, :tcp6, :unix, :unigram]
23
+ #=> :unigram specifies a datagram unix socket; if using a unix socket then set host and port to the "path"
24
+
25
+ :eager #=> default false, if set to "true" then all sockets will be initialized on pool initialization
26
+ :timeout #=> defaults to 5.0, the number of seconds to wait for an unused socket from the pool to be freed
27
+ :socketopts #=> an array of options to be set (via setsockopts) on each socket initialization
28
+ #=> example: [{:level => Socket::IPPROTO_TCP, :optname => Socket::TCP_NODELAY, :optval => 1}]
29
+
30
+ == Credits
31
+ * "socketpool" is heavily influenced by utils/pool.rb from MongoDB (Apache 2.0 licensed).
32
+ The socket checkout/checkin functionality is a direct copy, unfortunately all of the functionality I wanted (like eager initialization) was not
33
+ implemented so I augmented it for "socketpool".
34
+
35
+ == Contributing to socketpool
36
+
37
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
38
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
39
+ * Fork the project
40
+ * Start a feature/bugfix branch
41
+ * Commit and push until you are happy with your contribution
42
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
43
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
44
+
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+
11
+ require 'rake'
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ gem.name = "socketpool"
15
+ gem.homepage = "http://github.com/bdewitt/socketpool"
16
+ gem.license = "Apache 2.0"
17
+ gem.summary = "Provides class for managing a socket pool"
18
+ gem.description = "SocketPool class to mangage socket based client access [based on MongoDB pool class]"
19
+ gem.email = "brandon+socketpool@myjibe.com"
20
+ gem.authors = ["Brandon Dewitt"]
21
+ gem.add_development_dependency 'rspec', '> 2.0'
22
+ end
23
+ Jeweler::RubygemsDotOrgTasks.new
24
+
25
+ require 'rspec/core'
26
+ require 'rspec/core/rake_task'
27
+ RSpec::Core::RakeTask.new(:spec) do |spec|
28
+ spec.pattern = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
34
+ end
35
+
36
+ task :default => :spec
37
+
38
+ require 'rake/rdoctask'
39
+ Rake::RDocTask.new do |rdoc|
40
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
41
+
42
+ rdoc.rdoc_dir = 'rdoc'
43
+ rdoc.title = "socketpool #{version}"
44
+ rdoc.rdoc_files.include('README*')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/lib/socketpool.rb ADDED
@@ -0,0 +1,174 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'socket'
15
+ require 'thread'
16
+
17
+ class SocketPool
18
+
19
+ attr_accessor :host, :port, :size, :timeout, :checked_out
20
+
21
+ # Create a new socket pool
22
+ #
23
+ def initialize(host, port, opts={})
24
+ @host, @port = host, port
25
+
26
+ # Pool size and timeout.
27
+ @size = opts[:size] || 2
28
+ @timeout = opts[:timeout] || 5.0
29
+ @eager = opts[:eager] || false
30
+
31
+ # Mutex for synchronizing pool access
32
+ @connection_mutex = Mutex.new
33
+
34
+ # Condition variable for signal and wait
35
+ @queue = ConditionVariable.new
36
+
37
+ @socktype = opts[:type] || :tcp
38
+ @sockopts = opts[:socketopts].nil? ? [] : [opts[:socketopts]].flatten.inject([]){|s, so| s << so}
39
+ @sockets = []
40
+ @pids = {}
41
+ @checked_out = []
42
+
43
+ initialize_socketpool if @eager
44
+ end
45
+
46
+ def close
47
+ @sockets.each do |sock|
48
+ begin
49
+ sock.close
50
+ rescue IOError => ex
51
+ warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
52
+ end
53
+ end
54
+ @host = @port = nil
55
+ @sockets.clear
56
+ @pids.clear
57
+ @checked_out.clear
58
+ end
59
+
60
+ # Return a socket to the pool.
61
+ def checkin(socket)
62
+ @connection_mutex.synchronize do
63
+ @checked_out.delete(socket)
64
+ @queue.signal
65
+ end
66
+ true
67
+ end
68
+
69
+ # Adds a new socket to the pool and checks it out.
70
+ #
71
+ # This method is called exclusively from #checkout;
72
+ # therefore, it runs within a mutex.
73
+ def checkout_new_socket
74
+ begin
75
+ socket = Socket.new(so_domain(@socktype), so_type(@socktype), 0)
76
+ @sockaddr ||= Socket.pack_sockaddr_in(@port, @host) if ![:unix, :unigram].include?(@socktype)
77
+ @sockaddr ||= Socket.pack_sockaddr_un(@host) if [:unix, :unigram].include?(@socktype)
78
+ socket.connect(@sockaddr)
79
+ if @sockopts.size > 0
80
+ @sockopts.each{ |opt| socket.setsockopt(opt[:level], opt[:optname], opt[:optval]) }
81
+ end
82
+ rescue => ex
83
+ raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
84
+ end
85
+
86
+ @sockets << socket
87
+ @pids[socket] = Process.pid
88
+ @checked_out << socket
89
+ socket
90
+ end
91
+
92
+ # Checks out the first available socket from the pool.
93
+ #
94
+ # If the pid has changed, remove the socket and check out
95
+ # new one.
96
+ #
97
+ # This method is called exclusively from #checkout;
98
+ # therefore, it runs within a mutex.
99
+ def checkout_existing_socket
100
+ socket = (@sockets - @checked_out).first
101
+ if @pids[socket] != Process.pid
102
+ @pids[socket] = nil
103
+ @sockets.delete(socket)
104
+ socket.close
105
+ checkout_new_socket
106
+ else
107
+ @checked_out << socket
108
+ socket
109
+ end
110
+ end
111
+
112
+ # Check out an existing socket or create a new socket if the maximum
113
+ # pool size has not been exceeded. Otherwise, wait for the next
114
+ # available socket.
115
+ def checkout
116
+ start_time = Time.now
117
+ loop do
118
+ if (Time.now - start_time) > @timeout
119
+ raise ConnectionTimeoutError, "could not obtain connection within " +
120
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
121
+ "consider increasing the pool size or timeout."
122
+ end
123
+
124
+ @connection_mutex.synchronize do
125
+ socket = if @checked_out.size < @sockets.size
126
+ checkout_existing_socket
127
+ elsif @sockets.size < @size
128
+ checkout_new_socket
129
+ end
130
+
131
+ if socket
132
+ return socket
133
+ else
134
+ # Otherwise, wait
135
+ @queue.wait(@connection_mutex)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def so_type(val)
142
+ val = val.downcase.to_sym if val.respond_to?(:downcase) && val.respond_to?(:to_sym)
143
+ @so_type ||= {
144
+ :tcp => Socket::SOCK_STREAM,
145
+ :tcp6 => Socket::SOCK_STREAM,
146
+ :udp => Socket::SOCK_DGRAM,
147
+ :udp6 => Socket::SOCK_DGRAM,
148
+ :unix => Socket::SOCK_STREAM,
149
+ :unigram => Socket::SOCK_DGRAM
150
+ }[val]
151
+ end
152
+
153
+ def so_domain(val)
154
+ val = val.downcase.to_sym if val.respond_to?(:downcase) && val.respond_to?(:to_sym)
155
+ @so_domain ||= {
156
+ :tcp => Socket::AF_INET,
157
+ :tcp6 => Socket::AF_INET6,
158
+ :udp => Socket::AF_INET,
159
+ :udp6 => Socket::AF_INET6,
160
+ :unix => Socket::AF_UNIX,
161
+ :unigram => Socket::AF_UNIX
162
+ }[val]
163
+ end
164
+
165
+ private
166
+
167
+ def initialize_socketpool
168
+ begin
169
+ @size.times{ checkout_new_socket }
170
+ ensure
171
+ @checked_out = []
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,75 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "SocketPool arguments" do
4
+ describe SocketPool.new('127.0.0.1', '11222') do
5
+ specify { subject.port.should eq('11222')}
6
+ specify { subject.host.should eq('127.0.0.1')}
7
+ specify { subject.size.should eq(2)}
8
+ specify { subject.timeout.should eq(5.0)}
9
+ specify { subject.instance_variable_get(:@sockets).should eq([])}
10
+ specify { subject.instance_variable_get(:@checked_out).should eq([])}
11
+ specify { subject.instance_variable_get(:@pids).should eq({})}
12
+ specify { subject.instance_variable_get(:@eager).should eq(false)}
13
+ specify { subject.instance_variable_get(:@socktype).should eq(:tcp)}
14
+ specify { subject.instance_variable_get(:@sockopts).should eq([])}
15
+
16
+ it "should create socket on checkout" do
17
+ s = subject.checkout
18
+ subject.instance_variable_get(:@sockets).should_not eq([])
19
+ subject.instance_variable_get(:@sockets).size.should eq(1)
20
+ subject.instance_variable_get(:@checked_out).size.should eq(1)
21
+
22
+ subject.checkin(s)
23
+ subject.instance_variable_get(:@checked_out).size.should eq(0)
24
+ end
25
+ end
26
+
27
+ describe "UDP => Eager SocketPool" do
28
+ subject { SocketPool.new('127.0.0.1', '11222', :type => :udp, :eager => true) }
29
+
30
+ specify { subject.port.should eq('11222')}
31
+ specify { subject.host.should eq('127.0.0.1')}
32
+ specify { subject.size.should eq(2)}
33
+ specify { subject.timeout.should eq(5.0)}
34
+ specify { subject.instance_variable_get(:@sockets).size.should eq(2)}
35
+ specify { subject.instance_variable_get(:@checked_out).should eq([])}
36
+ specify { subject.instance_variable_get(:@eager).should eq(true)}
37
+ specify { subject.instance_variable_get(:@socktype).should eq(:udp)}
38
+ specify { subject.instance_variable_get(:@sockopts).should eq([])}
39
+ end
40
+
41
+ describe "UDP => Eager SocketPool => resized, short timeout" do
42
+ subject { SocketPool.new('127.0.0.1', '11222', :type => :udp, :eager => true, :size => 19, :timeout => 1) }
43
+
44
+ specify { subject.port.should eq('11222')}
45
+ specify { subject.host.should eq('127.0.0.1')}
46
+ specify { subject.size.should eq(19)}
47
+ specify { subject.timeout.should eq(1.0)}
48
+ specify { subject.instance_variable_get(:@sockets).size.should eq(19)}
49
+ specify { subject.instance_variable_get(:@checked_out).should eq([])}
50
+ specify { subject.instance_variable_get(:@eager).should eq(true)}
51
+ specify { subject.instance_variable_get(:@socktype).should eq(:udp)}
52
+ specify { subject.instance_variable_get(:@sockopts).should eq([])}
53
+ end
54
+
55
+ describe "TCP => Eager SocketPool => resized, short timeout => TCP_NODELAY" do
56
+ subject {
57
+ SocketPool.new('127.0.0.1', '11222',
58
+ :type => :tcp,
59
+ :eager => true,
60
+ :size => 19,
61
+ :timeout => 1,
62
+ :socketopts => [{:level => Socket::IPPROTO_TCP, :optname => Socket::TCP_NODELAY, :optval => 1}])
63
+ }
64
+
65
+ specify { subject.port.should eq('11222')}
66
+ specify { subject.host.should eq('127.0.0.1')}
67
+ specify { subject.size.should eq(19)}
68
+ specify { subject.timeout.should eq(1.0)}
69
+ specify { subject.instance_variable_get(:@sockets).size.should eq(19)}
70
+ specify { subject.instance_variable_get(:@checked_out).should eq([])}
71
+ specify { subject.instance_variable_get(:@eager).should eq(true)}
72
+ specify { subject.instance_variable_get(:@socktype).should eq(:tcp)}
73
+ specify { subject.instance_variable_get(:@sockopts).should_not eq([])}
74
+ end
75
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'socketpool'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: socketpool
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Brandon Dewitt
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-04-03 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :development
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 2
31
+ - 3
32
+ - 0
33
+ version: 2.3.0
34
+ name: rspec
35
+ version_requirements: *id001
36
+ prerelease: false
37
+ - !ruby/object:Gem::Dependency
38
+ type: :development
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 0
49
+ version: 1.0.0
50
+ name: bundler
51
+ version_requirements: *id002
52
+ prerelease: false
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 7
61
+ segments:
62
+ - 1
63
+ - 5
64
+ - 2
65
+ version: 1.5.2
66
+ name: jeweler
67
+ version_requirements: *id003
68
+ prerelease: false
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ name: rcov
81
+ version_requirements: *id004
82
+ prerelease: false
83
+ - !ruby/object:Gem::Dependency
84
+ type: :development
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">"
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 2
93
+ - 0
94
+ version: "2.0"
95
+ name: rspec
96
+ version_requirements: *id005
97
+ prerelease: false
98
+ description: SocketPool class to mangage socket based client access [based on MongoDB pool class]
99
+ email: brandon+socketpool@myjibe.com
100
+ executables: []
101
+
102
+ extensions: []
103
+
104
+ extra_rdoc_files:
105
+ - LICENSE.txt
106
+ - README.rdoc
107
+ files:
108
+ - .document
109
+ - .rspec
110
+ - Gemfile
111
+ - Gemfile.lock
112
+ - LICENSE.txt
113
+ - README.rdoc
114
+ - Rakefile
115
+ - VERSION
116
+ - lib/socketpool.rb
117
+ - spec/socketpool_spec.rb
118
+ - spec/spec_helper.rb
119
+ has_rdoc: true
120
+ homepage: http://github.com/bdewitt/socketpool
121
+ licenses:
122
+ - Apache 2.0
123
+ post_install_message:
124
+ rdoc_options: []
125
+
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 0
136
+ version: "0"
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ hash: 3
143
+ segments:
144
+ - 0
145
+ version: "0"
146
+ requirements: []
147
+
148
+ rubyforge_project:
149
+ rubygems_version: 1.6.2
150
+ signing_key:
151
+ specification_version: 3
152
+ summary: Provides class for managing a socket pool
153
+ test_files:
154
+ - spec/socketpool_spec.rb
155
+ - spec/spec_helper.rb