access_stack 0.0.1

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.
@@ -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