access_stack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 96173f1b78abdc2fee95955d17cc517d8d0ce49e
4
+ data.tar.gz: 4373cd19170488ebad5b76ef5a041f9b0ec6b36b
5
+ SHA512:
6
+ metadata.gz: a08d923069d7cb7d59f3ca5035c0c13e55ec44cf42e0a35c27fdbd31f518f93b2c10c0aad02c6873424dfa76e9298211640a9db2a1b646ceda5555569e556ab0
7
+ data.tar.gz: b956d7870b89fef820f4b36874c963c2137f3f3cfb31093f1f510bb1209c8e53025fb676cdb92dddbd694df7d398f9a36965b67a22d9aa079eed0ec25e1880ab
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ os:
3
+ - linux
4
+ rvm:
5
+ - 1.8.7
6
+ - 1.9.2
7
+ - 1.9.3
8
+ - 2.0.0
9
+ - 2.1.2
10
+ - ree
11
+ env:
12
+ - "CODECLIMATE_REPO_TOKEN=ce41872e2a5a6113de9ae79a83e48c01b53bd8aea3fb1b9cf93d18d1e82c4eb1"
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "codeclimate-test-reporter", group: :test, require: nil
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Nathaniel Symer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ # AccessStack
2
+
3
+ A general-purpose object "pool" for storing general objects. It can be a connection pool, cache, or even just a factory.
4
+
5
+ ## Notes
6
+
7
+ AccessStack is *very* threadsafe. It uses an internal stack to store the objects and any interaction with that is done with a `Mutex` lock in place.
8
+
9
+ AccessStack also implements most of the features present in ActiveRecord's connection pool, such as reaping, expiration, and timeout. It takes it a step farther by
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'access_stack'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install access_stack
24
+
25
+ ## Usage
26
+
27
+ You can create a stack:
28
+
29
+ require "access_stack"
30
+
31
+ s = AccessStack.new(
32
+ :size => 10,
33
+ :timeout => 3, # how long to wait for access to the stack
34
+ :expires => 5, # how long an object lasts in the stack
35
+ :create => lambda {
36
+ PG.connect
37
+ },
38
+ :destroy => lambda { |instance|
39
+ instance.close
40
+ },
41
+ :validate => lamda { |instance|
42
+ instance.status == CONNECTION_OK
43
+ }
44
+ )
45
+
46
+ Set a stack's blocks:
47
+
48
+ s.create = lambda {
49
+ # Do something
50
+ }
51
+
52
+ Use an instance:
53
+
54
+ res = s.with { |inst| inst.exec("SELECT * FROM users;") }
55
+ # res is a PG::Result now, since that's what the block returned
56
+
57
+ Eager-load instances:
58
+
59
+ s.create_objects 5 # Create and add 5 objects to the stack
60
+ s.fill # Fill the stack to capacity
61
+
62
+ Clear out instances:
63
+
64
+ s.reap! # Validates each object using expires and validate
65
+ s.empty! # Empties the stack
66
+ s.empty? # Checks if the stack is empty
67
+
68
+
69
+ ## TODO
70
+
71
+ 1. Validate based upon methods on objects in the pool
72
+ 2. Reap on an interval
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it ( https://github.com/fhsjaagshs/access_stack/fork )
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'access_stack/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "access_stack"
8
+ spec.version = AccessStack::VERSION
9
+ spec.authors = ["Nathaniel Symer"]
10
+ spec.email = ["nate@natesymer.com"]
11
+ spec.summary = %q{Abstract object pooling for cool people.}
12
+ spec.description = %q{Abstract object pooling for cool people. See homepage}
13
+ spec.homepage = "http://github.com/fhsjaagshs/access_stack"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,124 @@
1
+ require "access_stack/version"
2
+ require "thread"
3
+ require "timeout"
4
+
5
+ class AccessStack
6
+ attr_reader :count
7
+ attr_accessor :expires, :size, :timeout, :create, :destroy, :validate
8
+
9
+ TimeoutError = Class.new StandardError
10
+
11
+ def initialize(opts={})
12
+ @timeout = opts[:timeout] || opts["timeout"] || 5
13
+ @size = opts[:size] || opts["size"] || 5
14
+ @expires = opts[:expires] || opts["expires"] || -1
15
+ @expr_hash = {}
16
+ @stack = []
17
+ @count = 0
18
+ @mutex = Mutex.new
19
+ @create = opts[:create] || opts["create"]
20
+ @destroy = opts[:destroy] || opts["destroy"]
21
+ @validate = opts[:validate] || opts["validate"]
22
+ end
23
+
24
+ def with(&block)
25
+ begin
26
+ obj = nil
27
+
28
+ threadsafe do
29
+ obj = @stack.pop
30
+ end
31
+
32
+ if !(obj_valid obj)
33
+ obj = nil
34
+ @count -= 1
35
+ end
36
+
37
+ if @count < @size && obj.nil?
38
+ @count += 1
39
+ obj = create_obj
40
+ end
41
+
42
+ return block.call obj
43
+ ensure
44
+ threadsafe do
45
+ @stack.push obj
46
+ end
47
+ end
48
+ end
49
+
50
+ def reap!
51
+ return true if @count == 0
52
+ threadsafe do
53
+ @stack.reject(&method(:obj_valid)).each do |instance|
54
+ @destroy.call instance
55
+ @expr_hash.delete instance
56
+ @stack.delete instance
57
+ @count -= 1
58
+ end
59
+ end
60
+ end
61
+
62
+ def empty!
63
+ return if @count == 0
64
+ threadsafe do
65
+ @stack.each(&@destroy.method(:call))
66
+ @expr_hash.clear
67
+ @stack.clear
68
+ @count = 0
69
+ end
70
+ end
71
+
72
+ def fill
73
+ create_objects @count
74
+ end
75
+
76
+ def create_objects(num=1)
77
+ created_count = 0
78
+
79
+ threadsafe do
80
+ num.times do
81
+ if @count < @size
82
+ @stack.push create_obj
83
+ @count += 1
84
+ created_count += 1
85
+ end
86
+ end
87
+ end
88
+
89
+ created_count
90
+ end
91
+
92
+ def empty?
93
+ @count == 0
94
+ end
95
+
96
+ private
97
+
98
+ def threadsafe(&block)
99
+ begin
100
+ Timeout::timeout @timeout do
101
+ @mutex.lock
102
+ end
103
+
104
+ block.call
105
+ @mutex.unlock
106
+ true
107
+ rescue Timeout::Error
108
+ raise TimeoutError, "Failed to obtain a lock fast enough."
109
+ end
110
+ false
111
+ end
112
+
113
+ def create_obj
114
+ obj = @create.call
115
+ @expr_hash[obj] = Time.now
116
+ obj
117
+ end
118
+
119
+ def obj_valid(obj)
120
+ block_valid = @vaildate.call obj rescue true
121
+ expired = (@expires > 0 && @expr_hash[obj].to_f-Time.now.to_f > @expires)
122
+ !expired && block_valid
123
+ end
124
+ end
@@ -0,0 +1,3 @@
1
+ class AccessStack
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rspec"
4
+ require "access_stack"
5
+
6
+ def create_stack
7
+ AccessStack.new(
8
+ :size => 10,
9
+ :timeout => 3,
10
+ :create => lambda {
11
+ "THIS"
12
+ },
13
+ :destroy => lambda { |instance|
14
+ instance = nil
15
+ },
16
+ :validate => lambda { |instance|
17
+ instance.is_a? String && instance.length > 0
18
+ }
19
+ )
20
+ end
21
+
22
+ describe AccessStack do
23
+
24
+ it "should create objects" do
25
+ stack = create_stack
26
+ res = stack.with{ |inst| inst + "FOOBAR" }
27
+ stack.empty!
28
+ res == "THISFOOBAR"
29
+ end
30
+
31
+ it "should work concurrently" do
32
+ stack = create_stack
33
+ stack.create_objects 1
34
+
35
+ begin
36
+ t = []
37
+
38
+ t << Thread.new {
39
+ stack.with{ |inst| inst + "ONE" }
40
+ }
41
+
42
+ t << Thread.new {
43
+ stack.with{ |inst| inst + "TWO" }
44
+ }
45
+
46
+ t << Thread.new {
47
+ stack.with{ |inst| inst + "Three" }
48
+ }
49
+
50
+ t.each(&:join)
51
+ rescue StandardError => e
52
+ puts e.message
53
+ return false
54
+ end
55
+
56
+ stack.count == 3
57
+ end
58
+
59
+ it "should be able to time out" do
60
+ stack = create_stack
61
+ stack.timeout = 0.0000000000000001
62
+ begin
63
+ res = stack.with{ |inst| inst + "FOOBAR" }
64
+ rescue
65
+ return false
66
+ end
67
+ true
68
+ end
69
+
70
+ end
@@ -0,0 +1,87 @@
1
+ require "codeclimate-test-reporter"
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'bundler/setup'
5
+ Bundler.setup
6
+
7
+ require "./lib/access_stack.rb"
8
+
9
+
10
+ # This file was generated by the `rspec --init` command. Conventionally, all
11
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
12
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
13
+ # file to always be loaded, without a need to explicitly require it in any files.
14
+ #
15
+ # Given that it is always loaded, you are encouraged to keep this file as
16
+ # light-weight as possible. Requiring heavyweight dependencies from this file
17
+ # will add to the boot time of your test suite on EVERY test run, even for an
18
+ # individual file that may not need all of that loaded. Instead, make a
19
+ # separate helper file that requires this one and then use it only in the specs
20
+ # that actually need it.
21
+ #
22
+ # The `.rspec` file also contains a few flags that are not defaults but that
23
+ # users commonly want.
24
+ #
25
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
26
+ RSpec.configure do |config|
27
+ # The settings below are suggested to provide a good initial experience
28
+ # with RSpec, but feel free to customize to your heart's content.
29
+ =begin
30
+ # These two settings work together to allow you to limit a spec run
31
+ # to individual examples or groups you care about by tagging them with
32
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
33
+ # get run.
34
+ config.filter_run :focus
35
+ config.run_all_when_everything_filtered = true
36
+
37
+ # Many RSpec users commonly either run the entire suite or an individual
38
+ # file, and it's useful to allow more verbose output when running an
39
+ # individual spec file.
40
+ if config.files_to_run.one?
41
+ # Use the documentation formatter for detailed output,
42
+ # unless a formatter has already been configured
43
+ # (e.g. via a command-line flag).
44
+ config.default_formatter = 'doc'
45
+ end
46
+
47
+ # Print the 10 slowest examples and example groups at the
48
+ # end of the spec run, to help surface which specs are running
49
+ # particularly slow.
50
+ config.profile_examples = 10
51
+
52
+ # Run specs in random order to surface order dependencies. If you find an
53
+ # order dependency and want to debug it, you can fix the order by providing
54
+ # the seed, which is printed after each run.
55
+ # --seed 1234
56
+ config.order = :random
57
+
58
+ # Seed global randomization in this process using the `--seed` CLI option.
59
+ # Setting this allows you to use `--seed` to deterministically reproduce
60
+ # test failures related to randomization by passing the same `--seed` value
61
+ # as the one that triggered the failure.
62
+ Kernel.srand config.seed
63
+
64
+ # rspec-expectations config goes here. You can use an alternate
65
+ # assertion/expectation library such as wrong or the stdlib/minitest
66
+ # assertions if you prefer.
67
+ config.expect_with :rspec do |expectations|
68
+ # Enable only the newer, non-monkey-patching expect syntax.
69
+ # For more details, see:
70
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
71
+ expectations.syntax = :expect
72
+ end
73
+
74
+ # rspec-mocks config goes here. You can use an alternate test double
75
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
76
+ config.mock_with :rspec do |mocks|
77
+ # Enable only the newer, non-monkey-patching expect syntax.
78
+ # For more details, see:
79
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
80
+ mocks.syntax = :expect
81
+
82
+ # Prevents you from mocking or stubbing a method that does not exist on
83
+ # a real object. This is generally recommended.
84
+ mocks.verify_partial_doubles = true
85
+ end
86
+ =end
87
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: access_stack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nathaniel Symer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Abstract object pooling for cool people. See homepage
42
+ email:
43
+ - nate@natesymer.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - access_stack.gemspec
55
+ - lib/access_stack.rb
56
+ - lib/access_stack/version.rb
57
+ - spec/access_stack_spec.rb
58
+ - spec/spec_helper.rb
59
+ homepage: http://github.com/fhsjaagshs/access_stack
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.2.1
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Abstract object pooling for cool people.
83
+ test_files:
84
+ - spec/access_stack_spec.rb
85
+ - spec/spec_helper.rb