dispatch_queue_rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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