thread_order 0.1.0

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: 9a2dabe6630ddb96b43c686369e6a50f85a1d55c
4
+ data.tar.gz: aa1928df61b660fec67fbd4d33eb3f8214c8aefb
5
+ SHA512:
6
+ metadata.gz: b33ae8eafd3294c2f030f91e29268818eb5f1ec22f34a8e564b3731075033ba7c53dc6df07861932343e68858d3abf0d0e45a74bfdd2665e0cd6b9ebbdccbc81
7
+ data.tar.gz: a83df4acfce7b1d39bff123261b7ab12e0e8dc4cc35af6f973de76469873656f508360242427ea9c9f216b3d16fd4ae6056298e60d82709efc09c35bbf688c49
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ Gemfile.lock
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ script: bundle exec rspec
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.2
7
+ - 2.2.0
8
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2015 Josh Cheek
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 NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,2 @@
1
+ ThreadOrder
2
+ ===========
@@ -0,0 +1,93 @@
1
+ class ThreadOrder
2
+ def initialize
3
+ @bodies = {}
4
+ @threads = []
5
+ @queue = [] # we may not have thread stdlib required, so may not have Queue class
6
+ @mutex = Mutex.new
7
+ @worker = Thread.new { loop { work } }
8
+ @worker.abort_on_exception = true
9
+ end
10
+
11
+ def declare(name, &block)
12
+ @bodies[name] = block
13
+ end
14
+
15
+ def current
16
+ Thread.current[:thread_order_name]
17
+ end
18
+
19
+ def pass_to(name, options)
20
+ parent = Thread.current
21
+ child = nil
22
+ resume_event = extract_resume_event! options
23
+ resume_if = lambda do |event|
24
+ return unless event == sync { resume_event }
25
+ parent.wakeup
26
+ end
27
+
28
+ enqueue do
29
+ child = Thread.new do
30
+ enqueue { @threads << child }
31
+ sync { resume_event } == :sleep &&
32
+ enqueue { watch_for_sleep(child) { resume_if.call :sleep } }
33
+ begin
34
+ enqueue { resume_if.call :run }
35
+ Thread.current[:thread_order_name] = name
36
+ @bodies.fetch(name).call
37
+ rescue Exception => error
38
+ enqueue { parent.raise error }
39
+ raise
40
+ ensure
41
+ enqueue { resume_if.call :exit }
42
+ end
43
+ end
44
+ end
45
+
46
+ sleep
47
+ child
48
+ end
49
+
50
+ def apocalypse!(thread_method=:kill)
51
+ enqueue do
52
+ @threads.each(&thread_method)
53
+ @queue.clear
54
+ @worker.kill
55
+ end
56
+ @worker.join
57
+ end
58
+
59
+ private
60
+
61
+ def sync(&block)
62
+ @mutex.synchronize(&block)
63
+ end
64
+
65
+ def enqueue(&block)
66
+ sync { @queue << block }
67
+ end
68
+
69
+ def work
70
+ task = sync { @queue.shift }
71
+ task ||= lambda { Thread.pass }
72
+ task.call
73
+ end
74
+
75
+ def extract_resume_event!(options)
76
+ resume_on = options.delete :resume_on
77
+ options.any? &&
78
+ raise(ArgumentError, "Unknown options: #{options.inspect}")
79
+ resume_on && ![:run, :exit, :sleep].include?(resume_on) and
80
+ raise(ArgumentError, "Unknown status: #{resume_on.inspect}")
81
+ resume_on
82
+ end
83
+
84
+ def watch_for_sleep(thread, &cb)
85
+ if thread.status == false || thread.status == nil
86
+ # noop, dead threads dream no dreams
87
+ elsif thread.status == 'sleep'
88
+ cb.call
89
+ else
90
+ enqueue { watch_for_sleep(thread, &cb) }
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,126 @@
1
+ require 'thread_order'
2
+
3
+ RSpec.describe ThreadOrder do
4
+ let(:order) { described_class.new }
5
+ after { order.apocalypse! }
6
+
7
+ it 'allows thread behaviour to be declared and run by name' do
8
+ seen = []
9
+ order.declare(:third) { seen << :third }
10
+ order.declare(:first) { seen << :first; order.pass_to :second, :resume_on => :exit }
11
+ order.declare(:second) { seen << :second; order.pass_to :third, :resume_on => :exit }
12
+ expect(seen).to eq []
13
+ order.pass_to :first, :resume_on => :exit
14
+ expect(seen).to eq [:first, :second, :third]
15
+ end
16
+
17
+ it 'sleeps the thread which passed' do
18
+ main_thread = Thread.current
19
+ order.declare(:thread) { :noop until main_thread.status == 'sleep' }
20
+ order.pass_to :thread, :resume_on => :exit # passes if it doesn't lock up
21
+ end
22
+
23
+ context 'resume events' do
24
+ def self.test_status(name, statuses, *args, &threadmaker)
25
+ it "can resume the thread when the called thread enters #{name}", *args do
26
+ thread = instance_eval(&threadmaker)
27
+ statuses = Array statuses
28
+ expect(statuses).to include thread.status
29
+ end
30
+ end
31
+
32
+ test_status ':run', 'run' do
33
+ order.declare(:t) { loop { 1 } }
34
+ order.pass_to :t, :resume_on => :run
35
+ end
36
+
37
+ test_status ':sleep', 'sleep' do
38
+ order.declare(:t) { sleep }
39
+ order.pass_to :t, :resume_on => :sleep
40
+ end
41
+
42
+ # can't reproduce 'dead', but apparently JRuby 1.7.19 returned
43
+ # this on CI https://travis-ci.org/rspec/rspec-core/jobs/51933739
44
+ test_status ':exit', [false, 'aborting', 'dead'] do
45
+ order.declare(:t) { Thread.exit }
46
+ order.pass_to :t, :resume_on => :exit
47
+ end
48
+ end
49
+
50
+ describe 'errors in children' do
51
+ specify 'are raised in the child' do
52
+ child = nil
53
+ order.declare(:err) { child = Thread.current; raise 'the roof' }
54
+ order.pass_to :err, :resume_on => :exit rescue nil
55
+ child.join rescue nil
56
+ expect(child.status).to eq nil
57
+ end
58
+
59
+ specify 'are raised in the parent' do
60
+ order.declare(:err) { raise Exception, 'to the rules' }
61
+ expect {
62
+ order.pass_to :err, :resume_on => :run
63
+ loop { :noop }
64
+ }.to raise_error Exception, 'to the rules'
65
+ end
66
+
67
+ specify 'even if the parent is asleep' do
68
+ parent = Thread.current
69
+ order.declare(:err) {
70
+ :noop until parent.status == 'sleep'
71
+ raise 'the roof'
72
+ }
73
+ expect {
74
+ order.pass_to :err, :resume_on => :run
75
+ sleep
76
+ }.to raise_error RuntimeError, 'the roof'
77
+ end
78
+ end
79
+
80
+ it 'knows which thread is running' do
81
+ thread_names = []
82
+ order.declare(:a) {
83
+ thread_names << order.current
84
+ order.pass_to :b, :resume_on => :exit
85
+ thread_names << order.current
86
+ }
87
+ order.declare(:b) {
88
+ thread_names << order.current
89
+ }
90
+ order.pass_to :a, :resume_on => :exit
91
+ expect(thread_names.map(&:to_s).sort).to eq ['a', 'a', 'b']
92
+ end
93
+
94
+ it 'returns nil when asked for the current thread by one it did not define' do
95
+ thread_names = []
96
+ order.declare(:a) {
97
+ thread_names << order.current
98
+ Thread.new { thread_names << order.current }.join
99
+ }
100
+ expect(order.current).to eq nil
101
+ order.pass_to :a, :resume_on => :exit
102
+ expect(thread_names).to eq [:a, nil]
103
+ end
104
+
105
+ xit 'is implemented without depending on the stdlib' do
106
+ loaded_filenames = $LOADED_FEATURES.map { |filepath| File.basename filepath }
107
+ pending 'fucking fails :('
108
+ expect(loaded_filenames).to_not include 'monitor.rb'
109
+ expect(loaded_filenames).to_not include 'thread.rb'
110
+ expect(loaded_filenames).to_not include 'thread.bundle'
111
+ end
112
+
113
+ describe 'incorrect interface usage' do
114
+ it 'raises ArgumentError when told to resume on an unknown status' do
115
+ order.declare(:t) { }
116
+ expect { order.pass_to :t, :resume_on => :bad_status }.
117
+ to raise_error(ArgumentError, /bad_status/)
118
+ end
119
+
120
+ it 'raises an ArgumentError when you give it unknown keys (ie you spelled resume_on wrong)' do
121
+ order.declare(:t) { }
122
+ expect { order.pass_to :t, :bad_key => :t }.
123
+ to raise_error(ArgumentError, /bad_key/)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'thread_order'
3
+ s.version = '0.1.0'
4
+ s.licenses = ['MIT']
5
+ s.summary = "Test helper for ordering threaded code (does not depend on stdlib)"
6
+ s.description = "Test helper for ordering threaded code (does not depend on stdlib)."
7
+ s.authors = ["Josh Cheek"]
8
+ s.email = 'josh.cheek@gmail.com'
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- spec/*`.split("\n")
11
+ s.homepage = 'https://github.com/JoshCheek/thread_order'
12
+ s.add_development_dependency 'rspec', '~> 3.0'
13
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thread_order
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Cheek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ description: Test helper for ordering threaded code (does not depend on stdlib).
28
+ email: josh.cheek@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - ".gitignore"
34
+ - ".travis.yml"
35
+ - Gemfile
36
+ - License.txt
37
+ - Readme.md
38
+ - lib/thread_order.rb
39
+ - spec/thread_order_spec.rb
40
+ - thread_order.gemspec
41
+ homepage: https://github.com/JoshCheek/thread_order
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.4.1
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Test helper for ordering threaded code (does not depend on stdlib)
65
+ test_files:
66
+ - spec/thread_order_spec.rb
67
+ has_rdoc: