screw 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.
data/.gitignore ADDED
@@ -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
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ false and group :development, :test do
6
+ gem "guard"
7
+ gem "guard-bundler"
8
+ gem "guard-rspec"
9
+ gem "terminal-notifier-guard"
10
+ gem "pry-debugger" # required by Guard!
11
+ end
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard :bundler do
2
+ watch("Gemfile")
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
9
+ watch("spec/spec_helper.rb") { "spec" }
10
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mark Lanett
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.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Screw
2
+
3
+ Small classes to help bolt together a threaded application.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'screw'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install screw
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/[my-github-username]/screw/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/lib/screw.rb ADDED
@@ -0,0 +1,10 @@
1
+ require "screw/version"
2
+
3
+ module Screw
4
+ autoload :Logger, "screw/logger"
5
+ autoload :Proxy, "screw/proxy"
6
+ autoload :Queue, "screw/queue"
7
+ autoload :Semaphore, "screw/semaphore"
8
+
9
+ extend Logger
10
+ end
@@ -0,0 +1,57 @@
1
+ require "logger"
2
+ require "thread"
3
+
4
+ module Screw
5
+ module Logger
6
+
7
+ class SafeLogger
8
+
9
+ def initialize(theLogger)
10
+ @logger = theLogger
11
+ @mutex = Mutex.new
12
+ end
13
+
14
+ ::Logger::Severity.constants.each do |sym|
15
+ level = ::Logger::Severity.const_get(sym)
16
+ name = sym.to_s.downcase.to_sym
17
+ pred = "#{name}?".to_sym
18
+ define_method(name) do |message = nil, progname = nil, &block|
19
+ # Executing blocks in this mutex is not be a good idea. Resolve them first.
20
+ if block && (level >= @logger.level)
21
+ message = block.call.to_s
22
+ end
23
+ @mutex.synchronize do
24
+ @logger.add(level, message, progname)
25
+ end
26
+ end
27
+ define_method(pred) do
28
+ level >= @logger.level
29
+ end
30
+ end
31
+
32
+ def close
33
+ @mutex.synchronize do
34
+ @logger.close
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ # This logger will be shared by every class which includes the module. It's global.
41
+ def logger=(aLogger)
42
+ Thread.exclusive do
43
+ @@logger = aLogger
44
+ end
45
+ end
46
+
47
+ def logger
48
+ # I'm assuming that reads are atomic.
49
+ @@logger ||= begin
50
+ Thread.exclusive do
51
+ @@logger ||= SafeLogger.new(::Logger.new(STDERR))
52
+ end
53
+ end
54
+ end
55
+
56
+ end # Logger
57
+ end # Screw
@@ -0,0 +1,20 @@
1
+ module Screw
2
+ # Proxy allows you to intercept calls to one object and divert them to a handler.
3
+ # Each method call is memoized using method_missing/define_method.
4
+ class Proxy
5
+
6
+ def initialize(target, &block)
7
+ @target = target
8
+ @block = block
9
+ end
10
+
11
+ def method_missing(method, *arguments, &block)
12
+ raise ::NoMethodError, method.to_s if ! @target.respond_to?(method)
13
+ self.singleton_class.send(:define_method, method) do |*arguments,&block|
14
+ @block.call(method, *arguments, &block)
15
+ end
16
+ send(method, *arguments, &block)
17
+ end
18
+
19
+ end # Proxy
20
+ end # Screw
@@ -0,0 +1,61 @@
1
+ require "thread"
2
+
3
+ module Screw
4
+ class Queue
5
+
6
+ Unlimited = 0
7
+ Forever = (2 ** (0.size * 8 - 2) - 1) # Ruby uses an extra bit as a Fixnum flag.
8
+ NoWait = 0
9
+
10
+ class Timeout < ::Exception
11
+ end
12
+
13
+ # @param max is the maximum size of the queue. Optional, defaults to 0 (Unlimited).
14
+ def initialize(max = Unlimited)
15
+ raise max.inspect unless (Fixnum === max && max >= 0)
16
+ @max = max
17
+ @queue = []
18
+ @mutex = Mutex.new
19
+ @nempty = ConditionVariable.new
20
+ @nfull = ConditionVariable.new
21
+ end
22
+
23
+ def push(it)
24
+ @mutex.synchronize do
25
+ while @max > 0 && @queue.size >= @max do
26
+ @nfull.wait(@mutex)
27
+ end
28
+
29
+ # Push. But also if anyone else is waiting to pop, they can do it now.
30
+ @queue << it
31
+ @nempty.signal
32
+ self
33
+ end
34
+ end
35
+
36
+ # @param wait is timeout in seconds; Optional, defaults to Forever.
37
+ # @raises Timeout in the event of a timeout
38
+ def pop(wait = Forever)
39
+ raise wait.inspect unless (Fixnum === wait && wait >= 0)
40
+
41
+ @mutex.synchronize do
42
+ while @queue.size == 0
43
+
44
+ # Have we run out of time?
45
+ raise Timeout if wait <= 0
46
+
47
+ # Wait. Subtract wait time from timeout.
48
+ now = Time.now.to_i
49
+ @nempty.wait(@mutex, wait)
50
+ wait -= (Time.now.to_i - now)
51
+ end
52
+
53
+ # Pop
54
+ it = @queue.shift
55
+ @nfull.signal
56
+ it
57
+ end # synchronize
58
+ end # pop
59
+
60
+ end # Queue
61
+ end
@@ -0,0 +1,51 @@
1
+ require "thread"
2
+
3
+ module Screw
4
+ # Semaphore implements a resource counting control.
5
+ # Call #wait! to acquire a resource.
6
+ # Call #signal! to release one.
7
+ # Originally based on https://gist.github.com/pettyjamesm/3746457, but rewritten and fixed.
8
+ class Semaphore
9
+
10
+ def initialize(max = nil, count = 0)
11
+ max = max.to_i unless max.nil?
12
+ count = count.to_i
13
+ raise NonPositiveMax, max.to_s if max and max <= 0
14
+ raise CountOverflow, count.to_s if max and max < count
15
+ @max = max
16
+ @count = count
17
+ @mon = Mutex.new
18
+ @nempty = ConditionVariable.new
19
+ @nfull = ConditionVariable.new
20
+ end
21
+
22
+ def count
23
+ @mon.synchronize { @count }
24
+ end
25
+
26
+ # e.g. release (back into pool)
27
+ def signal!
28
+ @mon.synchronize do
29
+ @nfull.wait(@mon) while @max and @count == @max
30
+ @count += 1
31
+ @nempty.signal
32
+ end
33
+ end
34
+
35
+ # e.g. acquire (from pool)
36
+ def wait!
37
+ @mon.synchronize do
38
+ @nempty.wait(@mon) while @count == 0
39
+ @count -= 1
40
+ @nfull.signal
41
+ end
42
+ end
43
+
44
+ class NonPositiveMax < ArgumentError
45
+ end
46
+
47
+ class CountOverflow < ArgumentError
48
+ end
49
+
50
+ end # Semaphore
51
+ end # Screw
@@ -0,0 +1,3 @@
1
+ module Screw
2
+ VERSION = "0.0.1"
3
+ end
data/screw.gemspec ADDED
@@ -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 'screw/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "screw"
8
+ spec.version = Screw::VERSION
9
+ spec.authors = ["Mark Lanett"]
10
+ spec.email = ["mark.lanett@gmail.com"]
11
+ spec.summary = %q{Small classes to help bolt together a threaded application.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec"
23
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+ require "logger"
3
+ require "stringio"
4
+
5
+ describe Screw::Logger::SafeLogger do
6
+
7
+ let(:buffer) { StringIO.new("") }
8
+ let(:base) { ::Logger.new(buffer).tap { |it| it.formatter = ->(severity, datetime, program, message) { "#{message}\n" } } }
9
+ subject { Screw::Logger::SafeLogger.new(base) }
10
+
11
+ it "logs" do
12
+ subject.info "Hello, world"
13
+ expect(buffer.string).to eq "Hello, world\n"
14
+ end
15
+
16
+ it "resolves blocks" do
17
+ subject.info { "Hello, world" }
18
+ expect(buffer.string).to eq "Hello, world\n"
19
+ end
20
+
21
+ it "does not log to quiet loggers" do
22
+ base.level = 1
23
+ subject.debug "This is a test."
24
+ expect(buffer.string.size).to eq 0
25
+ subject.debug { "This is also a test." }
26
+ expect(buffer.string.size).to eq 0
27
+ end
28
+
29
+ it "does not interleave output", stress: true do
30
+ subject # force
31
+ ((1..100).map { Thread.new { subject.info "Hello, world" } }).shuffle.map(&:join)
32
+ expect(buffer.string).to eq "Hello, world\n" * 100
33
+ end
34
+
35
+ end
@@ -0,0 +1,70 @@
1
+ require "spec_helper"
2
+
3
+ class ProxyTest
4
+ attr_accessor :a, :b
5
+ def one(a)
6
+ self.a = a
7
+ self
8
+ end
9
+ def two(a,b)
10
+ self.a = a
11
+ self.b = b
12
+ self
13
+ end
14
+ def pass(&block)
15
+ block.call
16
+ end
17
+ end
18
+
19
+ describe Screw::Proxy do
20
+
21
+ let(:base) { ProxyTest.new }
22
+ subject { Screw::Proxy.new(base,&handler) }
23
+ let(:subject2) { Screw::Proxy.new(base,&handler) }
24
+
25
+ describe "for one argument" do
26
+ let(:handler) { ->(method, arg1, &block) { base.one(arg1) } }
27
+
28
+ it "memoizes methods" do
29
+ expect(subject).to_not respond_to(:one)
30
+
31
+ expect(subject.one(0)).to eq base
32
+ expect(subject).to respond_to(:one)
33
+
34
+ expect(subject2).to_not respond_to(:one)
35
+ end
36
+
37
+ it "passes one argument and returns the source" do
38
+ expect(subject.one(1)).to eq base
39
+ expect(base.a).to eq 1
40
+ end
41
+ end
42
+
43
+ describe "for two arguments" do
44
+ let(:handler) { ->(method, arg1, arg2, &block) { base.two(arg1,arg2) } }
45
+ it "passes one argument and returns the source" do
46
+ subject.two(2,3)
47
+ expect(base.a).to eq 2
48
+ expect(base.b).to eq 3
49
+ end
50
+ end
51
+
52
+ describe "for various arguments" do
53
+ let(:handler) { ->(method, *arguments, &block) { base.send(method,*arguments) } }
54
+ it "passes one argument and returns the source" do
55
+ subject.one(4)
56
+ expect(base.a).to eq 4
57
+ subject.two(5,6)
58
+ expect(base.a).to eq 5
59
+ expect(base.b).to eq 6
60
+ end
61
+ end
62
+
63
+ describe "with blocks" do
64
+ let(:handler) { ->(method, *arguments, &block) { base.send(method,*arguments,&block) } }
65
+ it "evaluates blocks" do
66
+ expect(subject.pass { 3 }).to eq 3
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Screw::Queue do
4
+
5
+ subject { Screw::Queue.new }
6
+
7
+ it "can push and pop ordered in same thread" do
8
+ subject.push :a
9
+ subject.push :b
10
+ expect(subject.pop).to eq :a
11
+ expect(subject.pop).to eq :b
12
+ end
13
+
14
+ it "can push and pop in different threads" do
15
+ Thread.new do
16
+ subject.push :a
17
+ subject.push :b
18
+ end
19
+ expect(subject.pop).to eq :a
20
+ expect(subject.pop).to eq :b
21
+ end
22
+
23
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Screw::Semaphore do
4
+
5
+ let(:num) { 25 }
6
+ let(:max) { 5 }
7
+ subject { Screw::Semaphore.new(nil, max) }
8
+ before { expect(subject.count).to eq max }
9
+ after { expect(subject.count).to eq max }
10
+
11
+ it "does not deadlock", stress: true do
12
+ t = (1..num).map do
13
+ Thread.new do
14
+ subject.wait!
15
+ sleep rand
16
+ subject.signal!
17
+ end
18
+ end
19
+ t.map(&:join)
20
+ end
21
+
22
+ end
@@ -0,0 +1,60 @@
1
+ require "screw"
2
+
3
+ RSpec.configure do |config|
4
+ # These two settings work together to allow you to limit a spec run
5
+ # to individual examples or groups you care about by tagging them with
6
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
7
+ # get run.
8
+ config.filter_run :focus
9
+ config.run_all_when_everything_filtered = true
10
+
11
+ # Many RSpec users commonly either run the entire suite or an individual
12
+ # file, and it's useful to allow more verbose output when running an
13
+ # individual spec file.
14
+ if config.files_to_run.one?
15
+ # Use the documentation formatter for detailed output,
16
+ # unless a formatter has already been configured
17
+ # (e.g. via a command-line flag).
18
+ config.default_formatter = 'doc'
19
+ end
20
+
21
+ # Print the 10 slowest examples and example groups at the
22
+ # end of the spec run, to help surface which specs are running
23
+ # particularly slow.
24
+ config.profile_examples = 10
25
+
26
+ # Run specs in random order to surface order dependencies. If you find an
27
+ # order dependency and want to debug it, you can fix the order by providing
28
+ # the seed, which is printed after each run.
29
+ # --seed 1234
30
+ config.order = :random
31
+
32
+ # Seed global randomization in this process using the `--seed` CLI option.
33
+ # Setting this allows you to use `--seed` to deterministically reproduce
34
+ # test failures related to randomization by passing the same `--seed` value
35
+ # as the one that triggered the failure.
36
+ Kernel.srand config.seed
37
+
38
+ # rspec-expectations config goes here. You can use an alternate
39
+ # assertion/expectation library such as wrong or the stdlib/minitest
40
+ # assertions if you prefer.
41
+ config.expect_with :rspec do |expectations|
42
+ # Enable only the newer, non-monkey-patching expect syntax.
43
+ # For more details, see:
44
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
45
+ expectations.syntax = :expect
46
+ end
47
+
48
+ # rspec-mocks config goes here. You can use an alternate test double
49
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
50
+ config.mock_with :rspec do |mocks|
51
+ # Enable only the newer, non-monkey-patching expect syntax.
52
+ # For more details, see:
53
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
54
+ mocks.syntax = :expect
55
+
56
+ # Prevents you from mocking or stubbing a method that does not exist on
57
+ # a real object. This is generally recommended.
58
+ mocks.verify_partial_doubles = true
59
+ end
60
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: screw
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Lanett
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-07-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description:
63
+ email:
64
+ - mark.lanett@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - .rspec
71
+ - Gemfile
72
+ - Guardfile
73
+ - LICENSE.txt
74
+ - README.md
75
+ - Rakefile
76
+ - lib/screw.rb
77
+ - lib/screw/logger.rb
78
+ - lib/screw/proxy.rb
79
+ - lib/screw/queue.rb
80
+ - lib/screw/semaphore.rb
81
+ - lib/screw/version.rb
82
+ - screw.gemspec
83
+ - spec/lib/screw/logger_spec.rb
84
+ - spec/lib/screw/proxy_spec.rb
85
+ - spec/lib/screw/queue_spec.rb
86
+ - spec/lib/screw/semaphore_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: ''
89
+ licenses:
90
+ - MIT
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ segments:
102
+ - 0
103
+ hash: 3114135878833884078
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ segments:
111
+ - 0
112
+ hash: 3114135878833884078
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 1.8.23
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Small classes to help bolt together a threaded application.
119
+ test_files:
120
+ - spec/lib/screw/logger_spec.rb
121
+ - spec/lib/screw/proxy_spec.rb
122
+ - spec/lib/screw/queue_spec.rb
123
+ - spec/lib/screw/semaphore_spec.rb
124
+ - spec/spec_helper.rb