dispatch_queue_rb 1.0.0

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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/LICENSE +22 -0
  4. data/README.md +98 -0
  5. data/dispatch_queue_rb.gemspec +43 -0
  6. data/lib/dispatch_queue_rb.rb +32 -0
  7. data/lib/dispatch_queue_rb/concurrent_queue.rb +116 -0
  8. data/lib/dispatch_queue_rb/dispatch.rb +66 -0
  9. data/lib/dispatch_queue_rb/dispatch_group.rb +72 -0
  10. data/lib/dispatch_queue_rb/internal/condition_variable_pool.rb +33 -0
  11. data/lib/dispatch_queue_rb/internal/continuation.rb +41 -0
  12. data/lib/dispatch_queue_rb/internal/heap.rb +88 -0
  13. data/lib/dispatch_queue_rb/internal/thread_pool_queue.rb +127 -0
  14. data/lib/dispatch_queue_rb/internal/thread_queue.rb +62 -0
  15. data/lib/dispatch_queue_rb/internal/timer_pool.rb +71 -0
  16. data/lib/dispatch_queue_rb/mixins/dispatch_after_impl.rb +18 -0
  17. data/lib/dispatch_queue_rb/mixins/dispatch_sync_impl.rb +42 -0
  18. data/lib/dispatch_queue_rb/serial_queue.rb +77 -0
  19. data/lib/dispatch_queue_rb/version.rb +12 -0
  20. data/rakefile.rb +110 -0
  21. data/test/_test_env.rb +52 -0
  22. data/test/test_concurrent_queue.rb +90 -0
  23. data/test/test_condition_variable_pool.rb +41 -0
  24. data/test/test_continuation.rb +23 -0
  25. data/test/test_dispatch.rb +91 -0
  26. data/test/test_dispatch_group.rb +59 -0
  27. data/test/test_group_concurrent_queue.rb +75 -0
  28. data/test/test_group_serial_queue.rb +33 -0
  29. data/test/test_group_thread_pool_queue.rb +34 -0
  30. data/test/test_heap.rb +58 -0
  31. data/test/test_serial_queue.rb +77 -0
  32. data/test/test_thread_pool_queue.rb +63 -0
  33. data/test/test_thread_queue.rb +77 -0
  34. data/test/test_timer_pool.rb +124 -0
  35. data/test/test_version.rb +155 -0
  36. metadata +181 -0
@@ -0,0 +1,77 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_thread_queue.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+ require '_test_env.rb'
12
+
13
+ module DispatchQueue
14
+ describe ThreadQueue do
15
+
16
+ let( :task_count ) { 4 }
17
+ let( :log ) { [] }
18
+ let( :lock ) { Mutex.new }
19
+ let( :logger ) { Proc.new { |e, d| lock.synchronize { log << [e, d] } } }
20
+ subject { ThreadQueue.new }
21
+
22
+ it "can initialize a thread queue" do
23
+ subject.must_be_instance_of ThreadQueue
24
+ end
25
+
26
+ def task( task_id )
27
+ logger.call( :begin_task, { task_id:task_id, thread:Thread.current } )
28
+ sleep(0.001)
29
+ logger.call( :end_task, { task_id:task_id, thread:Thread.current } )
30
+ end
31
+
32
+ describe "dispatch_async" do
33
+ it "executes tasks serially" do
34
+ (1..task_count).each { |i| subject.dispatch_async { task( i ) } }
35
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
36
+
37
+ log.map { |e,d| e }.must_equal [:begin_task, :end_task] * task_count
38
+ end
39
+
40
+ it "executes tasks in enqueue order" do
41
+ (1..task_count).each { |i| subject.dispatch_async { task( i ) } }
42
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
43
+
44
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
45
+ task_id_list.must_equal (1..task_count).to_a
46
+ end
47
+
48
+ it "resumes execution when new tasks are enqueues" do
49
+ (1..task_count).each { |i| subject.dispatch_async { task( "a#{i}".to_sym ) } }
50
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
51
+ sleep( 0.01 )
52
+
53
+ (1..task_count).each { |i| subject.dispatch_async { task( "b#{i}".to_sym ) } }
54
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
55
+
56
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
57
+ task_id_list.count.must_equal task_count*2, task_id_list
58
+ end
59
+ end
60
+
61
+ describe "with multiple thread queues" do
62
+ let( :subject2 ) { ThreadQueue.new }
63
+
64
+ it "interleaves tasks from different queues" do
65
+ (1..task_count).each { |i| subject.dispatch_async { task( "a#{i}".to_sym ) } }
66
+ (1..task_count).each { |i| subject2.dispatch_async { task( "b#{i}".to_sym ) } }
67
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
68
+ subject2.dispatch_barrier_sync {} # wait for all previous tasks to complete
69
+
70
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
71
+ task_chunks = task_id_list.chunk { |e| e.to_s[0] }.map { |c, l| l }
72
+ task_chunks.count.must_be :>, 3, task_chunks
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,124 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_timer_pool.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+ require '_test_env.rb'
12
+
13
+ module DispatchQueue
14
+ describe TimerPool do
15
+ let( :group ) { DispatchGroup.new }
16
+ let( :queue ) { SerialQueue.new }
17
+ let( :reference_time ) { Time.now + 0.005 }
18
+ let( :result ) { [] }
19
+
20
+ describe "default_pool" do
21
+ subject { TimerPool.default_pool }
22
+
23
+ it "is a valid TimerPool" do
24
+ subject.must_be_instance_of TimerPool
25
+ end
26
+
27
+ it "execute tasks after specified time" do
28
+ done = false
29
+ subject.dispatch_after( Time.now() + 0.001 ) { done = true }
30
+ done.must_equal false
31
+ sleep( 0.002 )
32
+ done.must_equal true
33
+ end
34
+
35
+ it "execute tasks after specified delay" do
36
+ done = false
37
+ subject.dispatch_after( 0.001 ) { done = true }
38
+ done.must_equal false
39
+ sleep( 0.002 )
40
+ done.must_equal true
41
+ end
42
+
43
+ it "execute tasks in eta order" do
44
+ subject.dispatch_after( reference_time + 0.001, target_queue:queue, group:group ) { result << 1 }
45
+ subject.dispatch_after( reference_time + 0.003, target_queue:queue, group:group ) { result << 3 }
46
+ subject.dispatch_after( reference_time + 0.004, target_queue:queue, group:group ) { result << 4 }
47
+ subject.dispatch_after( reference_time + 0.002, target_queue:queue, group:group ) { result << 2 }
48
+ group.wait()
49
+ result.must_equal [1,2,3,4]
50
+ end
51
+ end # describe "default_pool"
52
+
53
+ describe "SerialQueue.dispatch_after" do
54
+ subject { SerialQueue.new }
55
+
56
+ it "execute tasks in eta order" do
57
+ subject.dispatch_after( reference_time + 0.001, group:group ) { result << 1 }
58
+ subject.dispatch_after( reference_time + 0.003, group:group ) { result << 3 }
59
+ subject.dispatch_after( reference_time + 0.004, group:group ) { result << 4 }
60
+ subject.dispatch_after( reference_time + 0.002, group:group ) { result << 2 }
61
+ group.wait()
62
+ result.must_equal [1,2,3,4]
63
+ end
64
+
65
+ it "execute more tasks in eta order" do
66
+ count = 100
67
+ (1..count).to_a.shuffle.each do |i|
68
+ subject.dispatch_after( reference_time + i * 0.001, group:group ) { result << i }
69
+ end
70
+ group.wait()
71
+ result.must_equal (1..count).to_a
72
+ end
73
+ end
74
+
75
+ describe "ThreadQueue.dispatch_after" do
76
+ subject { ThreadQueue.new }
77
+
78
+ it "execute tasks in eta order" do
79
+ subject.dispatch_after( reference_time + 0.001, group:group ) { result << 1 }
80
+ subject.dispatch_after( reference_time + 0.003, group:group ) { result << 3 }
81
+ subject.dispatch_after( reference_time + 0.004, group:group ) { result << 4 }
82
+ subject.dispatch_after( reference_time + 0.002, group:group ) { result << 2 }
83
+ group.wait()
84
+ result.must_equal [1,2,3,4]
85
+ end
86
+ end
87
+
88
+
89
+ # NOTE: these test may fail occasionally since the target queues uses are
90
+ # concurrent and only guaranty that work will be scheduled in order.
91
+ # Execution might be out of order by the time the result is enqueued.
92
+ # Ordering relies exclusively on the delays introduced by dispatch_after(),
93
+ # which might not be nough.
94
+ # Also note that there is no synchronization on writing to result, so this
95
+ # will result in a race condition in multi-threaded ruby interpretter.
96
+
97
+ describe "ConcurrentQueue.dispatch_after" do
98
+ subject { ConcurrentQueue.new }
99
+
100
+ it "execute tasks in eta order" do
101
+ subject.dispatch_after( reference_time + 0.001, group:group ) { result << 1 }
102
+ subject.dispatch_after( reference_time + 0.003, group:group ) { result << 3 }
103
+ subject.dispatch_after( reference_time + 0.004, group:group ) { result << 4 }
104
+ subject.dispatch_after( reference_time + 0.002, group:group ) { result << 2 }
105
+ group.wait()
106
+ result.must_equal [1,2,3,4]
107
+ end
108
+ end
109
+
110
+ describe "ThreadPoolQueue.dispatch_after" do
111
+ subject { ThreadPoolQueue.new }
112
+
113
+ it "execute tasks in eta order" do
114
+ subject.dispatch_after( reference_time + 0.001, group:group ) { result << 1 }
115
+ subject.dispatch_after( reference_time + 0.003, group:group ) { result << 3 }
116
+ subject.dispatch_after( reference_time + 0.004, group:group ) { result << 4 }
117
+ subject.dispatch_after( reference_time + 0.002, group:group ) { result << 2 }
118
+ group.wait()
119
+ result.must_equal [1,2,3,4]
120
+ end
121
+ end
122
+
123
+ end # describe TimerPool
124
+ end # module DispatchQueue
@@ -0,0 +1,155 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_version.rb
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+ require '_test_env.rb'
12
+
13
+ module DispatchQueue
14
+ describe VERSION do
15
+
16
+ subject { VERSION }
17
+
18
+ it "is definied as a string" do
19
+ subject.must_be_instance_of String
20
+ end
21
+
22
+ end
23
+
24
+
25
+ class Expectation
26
+ def initialize( target, action=nil, *args, **opts, &block )
27
+ @target = target
28
+ @action = action
29
+ @args = args
30
+ @opts = opts
31
+ @block = block
32
+ end
33
+
34
+ def to
35
+ self
36
+ end
37
+
38
+ def to_be
39
+ self
40
+ end
41
+
42
+ def method_missing( name, *args, **opts, &block )
43
+ # puts "method_missing( #{name.inspect} )"
44
+ Expectation.new( self, name, *args, **opts, &block )
45
+ end
46
+
47
+ def inspect
48
+ "#{@target.inspect}#{_inspect_action()}"
49
+ end
50
+
51
+ def ==( arg )
52
+ Expectation.new( self, :==, arg )
53
+ end
54
+
55
+ def !=( arg )
56
+ Expectation.new( self, :!=, arg )
57
+ end
58
+
59
+ def =~( arg, within:nil )
60
+ Expectation.new( self, :=~, arg, within:within )
61
+ end
62
+
63
+
64
+ private
65
+ def _inspect_action()
66
+ return "" if @action.nil?
67
+ return " #{@action} #{@args[0]}" if _is_operator_action()
68
+ return ".#{@action}" if @args.empty? && @opts.empty?
69
+ return ".#{@action}(#{_inspect_action_args()})"
70
+ end
71
+
72
+ def _is_operator_action()
73
+ @args.count == 1 && [:==, :!=, :=~, :<=>, :<, :>, :<=, :>=, :<<, :>>].include?( @action )
74
+ end
75
+
76
+
77
+ def _inspect_action_args()
78
+ return "" if @args.empty? && @opts.empty?
79
+ return " " + ( @args.map { |a| a.inspect } +
80
+ @opts.map { |k,v| "#{k}: #{v.inspect}" } ).join( ', ' ) + " "
81
+ end
82
+ end
83
+
84
+
85
+ # expect( subject ) < 10
86
+ # expect( subject ).start_with?( "aaa" )
87
+ # expect( subject ).to.start_with?( "aaa" )
88
+ # expect( subject ).to.include?( "aaa" )
89
+ # expect( subject ).count < 10
90
+ # expect( subject ).count.to_equal( 10 ).within( 20 )
91
+ # expect( subject ).count.to_equal( 10 ).within( 20 )
92
+
93
+ describe Expectation do
94
+
95
+ def expect( value )
96
+ Expectation.new( value )
97
+ end
98
+
99
+ it "can create a new expectation" do
100
+ expectation = expect( :value )
101
+ expectation.must_be_instance_of Expectation
102
+ end
103
+
104
+ it "inspects to the wrapped value" do
105
+ expectation = expect( :value )
106
+ expectation.inspect.must_equal ":value"
107
+ end
108
+
109
+ it "inspects with method call" do
110
+ expectation = expect( :value ).something()
111
+ expectation.inspect.must_equal ":value.something"
112
+ end
113
+
114
+ it "inspects with method call and arguments" do
115
+ expectation = expect( :value ).something(:aaa, :bbb)
116
+ expectation.inspect.must_equal ":value.something( :aaa, :bbb )"
117
+ end
118
+
119
+ it "inspects with method call and optional arguments" do
120
+ expectation = expect( :value ).something(:aaa, bbb:1)
121
+ expectation.inspect.must_equal ":value.something( :aaa, bbb: 1 )"
122
+ end
123
+
124
+ it "inspects with chained method calls" do
125
+ expectation = expect( :value ).something(:aaa, :bbb).something_else( aaa:1, bbb:2 )
126
+ expectation.inspect.must_equal ":value.something( :aaa, :bbb ).something_else( aaa: 1, bbb: 2 )"
127
+ end
128
+
129
+ it "inspects with chained method calls and binary operators" do
130
+ expectation = expect( :value ).something(:aaa, :bbb) < 10
131
+ expectation.inspect.must_equal ":value.something( :aaa, :bbb ) < 10"
132
+ end
133
+
134
+ it "inspects with chained method calls and binary operators 2" do
135
+ expectation = expect( :value ).something(:aaa, :bbb).to_be << 10
136
+ expectation.inspect.must_equal ":value.something( :aaa, :bbb ) << 10"
137
+ end
138
+
139
+ it "inspects with chained method calls and binary operators 3" do
140
+ expectation = expect( [1,2,3] ).size == 10
141
+ expectation.inspect.must_equal "[1, 2, 3].size == 10"
142
+ end
143
+
144
+ it "inspects with chained method calls and binary operators 4" do
145
+ expectation = expect( [1,2,3] ).size != 10
146
+ expectation.inspect.must_equal "[1, 2, 3].size != 10"
147
+ end
148
+
149
+ it "inspects with chained method calls and binary operators 4" do
150
+ expectation = expect( [1,2,3] ).size.=~( 10, within:1 )
151
+ expectation.inspect.must_equal "[1, 2, 3].size =~ 10"
152
+ end
153
+
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,181 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dispatch_queue_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Marc-Antoine Argenton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-06 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.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: watch
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '0.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rr
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '5.3'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '5.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest-reporters
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ description: DispatchQueueRb is a pure ruby implementation of Grand Central Dispatch
98
+ concurrency primitives. It implements serial and concurrent queues, with synchronous,
99
+ asynchronous, barrier and delayed dispatch methods. All queues dispatch methods
100
+ support an optional dispatch groups to synchronize on completion of a group of tasks.
101
+ It also provides a thread pool based concurrent queue, scaled to the number of available
102
+ cpu cores, and used by default to schedule the actual work.
103
+ email:
104
+ - maargenton.dev@gmail.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - Gemfile
110
+ - LICENSE
111
+ - README.md
112
+ - dispatch_queue_rb.gemspec
113
+ - lib/dispatch_queue_rb.rb
114
+ - lib/dispatch_queue_rb/concurrent_queue.rb
115
+ - lib/dispatch_queue_rb/dispatch.rb
116
+ - lib/dispatch_queue_rb/dispatch_group.rb
117
+ - lib/dispatch_queue_rb/internal/condition_variable_pool.rb
118
+ - lib/dispatch_queue_rb/internal/continuation.rb
119
+ - lib/dispatch_queue_rb/internal/heap.rb
120
+ - lib/dispatch_queue_rb/internal/thread_pool_queue.rb
121
+ - lib/dispatch_queue_rb/internal/thread_queue.rb
122
+ - lib/dispatch_queue_rb/internal/timer_pool.rb
123
+ - lib/dispatch_queue_rb/mixins/dispatch_after_impl.rb
124
+ - lib/dispatch_queue_rb/mixins/dispatch_sync_impl.rb
125
+ - lib/dispatch_queue_rb/serial_queue.rb
126
+ - lib/dispatch_queue_rb/version.rb
127
+ - rakefile.rb
128
+ - test/_test_env.rb
129
+ - test/test_concurrent_queue.rb
130
+ - test/test_condition_variable_pool.rb
131
+ - test/test_continuation.rb
132
+ - test/test_dispatch.rb
133
+ - test/test_dispatch_group.rb
134
+ - test/test_group_concurrent_queue.rb
135
+ - test/test_group_serial_queue.rb
136
+ - test/test_group_thread_pool_queue.rb
137
+ - test/test_heap.rb
138
+ - test/test_serial_queue.rb
139
+ - test/test_thread_pool_queue.rb
140
+ - test/test_thread_queue.rb
141
+ - test/test_timer_pool.rb
142
+ - test/test_version.rb
143
+ homepage: https://github.com/marcus999/dispatch_queue_rb
144
+ licenses: []
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.4.5
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: Pure ruby implementation of Grand Central Dispatch concurrency primitives.
166
+ test_files:
167
+ - test/_test_env.rb
168
+ - test/test_concurrent_queue.rb
169
+ - test/test_condition_variable_pool.rb
170
+ - test/test_continuation.rb
171
+ - test/test_dispatch.rb
172
+ - test/test_dispatch_group.rb
173
+ - test/test_group_concurrent_queue.rb
174
+ - test/test_group_serial_queue.rb
175
+ - test/test_group_thread_pool_queue.rb
176
+ - test/test_heap.rb
177
+ - test/test_serial_queue.rb
178
+ - test/test_thread_pool_queue.rb
179
+ - test/test_thread_queue.rb
180
+ - test/test_timer_pool.rb
181
+ - test/test_version.rb