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,91 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_dispatch.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 Dispatch do
15
+ describe "default_queue" do
16
+ it "is a ThreadPoolQueue" do
17
+ Dispatch.default_queue.must_be_instance_of ThreadPoolQueue
18
+ end
19
+
20
+ it "must remain the same" do
21
+ q1 = Dispatch.default_queue
22
+ q2 = Dispatch.default_queue
23
+
24
+ q1.wont_be_nil
25
+ q2.wont_be_nil
26
+ q2.must_be_same_as q1
27
+ end
28
+ end
29
+
30
+ describe "main_queue" do
31
+ it "is a ThreadQueue" do
32
+ Dispatch.main_queue.must_be_instance_of ThreadQueue
33
+ end
34
+
35
+ it "must remain the same" do
36
+ q1 = Dispatch.main_queue
37
+ q2 = Dispatch.main_queue
38
+
39
+ q1.wont_be_nil
40
+ q2.wont_be_nil
41
+ q2.must_be_same_as q1
42
+ end
43
+ end
44
+
45
+ describe "ncpu" do
46
+ it "returns the number of cpu cores available" do
47
+ Dispatch.ncpu.must_be_kind_of Integer
48
+ Dispatch.ncpu.must_be :>, 1
49
+ end
50
+ end
51
+
52
+ describe "synchronize" do
53
+ it "synchronizes an asynchronous operation with the calling thread" do
54
+ done = false
55
+ assert_wont_timeout do
56
+ Dispatch.synchronize do |completion_handler|
57
+ Dispatch.default_queue.dispatch_async do
58
+ done = true
59
+ completion_handler.call()
60
+ end
61
+ end
62
+ end
63
+ done.must_equal true
64
+ end
65
+
66
+ it "return its result to the calling thread" do
67
+ assert_wont_timeout do
68
+ Dispatch.synchronize do |completion_handler|
69
+ Dispatch.default_queue.dispatch_async do
70
+ completion_handler.call( :result )
71
+ end
72
+ end.must_equal :result
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "concurrent_map" do
78
+ it "returns reslt of concurrently executed block" do
79
+ start_time = Time.now()
80
+ result = Dispatch.concurrent_map( 1..10 ) do |i|
81
+ sleep( 0.001 )
82
+ i
83
+ end.must_equal (1..10).to_a
84
+
85
+ elapsed_time = Time.now - start_time
86
+ elapsed_time.must_be :<, 0.005
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,59 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_dispatch_group.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
+ require 'timeout'
13
+
14
+ module DispatchQueue
15
+ describe DispatchGroup do
16
+
17
+ subject { DispatchGroup.new }
18
+
19
+ it "can create a new group" do
20
+ subject.must_be_instance_of DispatchGroup
21
+ end
22
+
23
+
24
+ describe "wait()" do
25
+ it "returns true immediatly when no work is pending in the group" do
26
+ subject.wait().must_equal true
27
+ end
28
+
29
+ it "waits forever if group enters and never leaves" do
30
+ subject.enter()
31
+
32
+ assert_must_timeout do
33
+ subject.wait()
34
+ end
35
+ end
36
+
37
+ it "waits until timeout expires and returns false if enter never leaves " do
38
+ subject.enter()
39
+
40
+ assert_wont_timeout(0.002) do
41
+ subject.wait( timeout:0.001 ).must_equal false
42
+ end
43
+ end
44
+
45
+ it "returns true when leave() is called" do
46
+ subject.enter()
47
+ Dispatch.default_queue.dispatch_async do
48
+ sleep( 0.001 )
49
+ subject.leave()
50
+ end
51
+
52
+ assert_wont_timeout(0.002) do
53
+ subject.wait().must_equal true
54
+ end
55
+ end
56
+ end # describe "wait()"
57
+
58
+ end # describe DispatchGroup do
59
+ end # module DispatchQueue
@@ -0,0 +1,75 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_group_concurrent_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
+ require 'timeout'
13
+
14
+ module DispatchQueue
15
+ describe "DispatchGroup with ConcurrentQueue" do
16
+ let( :target_queue ) { ConcurrentQueue.new }
17
+ let( :group ) { DispatchGroup.new }
18
+
19
+ it "leaves group when task completes, with single task on idle queue " do
20
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
21
+ assert_must_timeout( 0.001 ) { group.wait() }
22
+ assert_wont_timeout( 0.002 ) { group.wait() }
23
+ end
24
+
25
+ it "leaves group when task completes, with enquened tasks" do
26
+ (1..10).each do
27
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
28
+ end
29
+
30
+ assert_must_timeout( 0.001 ) { group.wait() }
31
+ assert_wont_timeout( 0.020 ) { group.wait() }
32
+ end
33
+
34
+ it "leaves group when task completes, with barrier tasks only" do
35
+ (1..4).each do
36
+ target_queue.dispatch_barrier_async( group:group ) { sleep( 0.002 ) }
37
+ end
38
+
39
+ assert_must_timeout( 0.001 ) { group.wait() }
40
+ assert_wont_timeout( 0.010 ) { group.wait() }
41
+ end
42
+
43
+ it "leaves group when task completes, with mix of barrier and non-barrier tasks" do
44
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
45
+ (1..10).each do
46
+ target_queue.dispatch_async( group:group ) { sleep( 0.001 ) }
47
+ end
48
+
49
+ target_queue.dispatch_barrier_async( group:group ) { sleep( 0.001 ) }
50
+ (1..10).each do
51
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
52
+ end
53
+ target_queue.dispatch_barrier_async( group:group ) { sleep( 0.001 ) }
54
+
55
+ assert_must_timeout( 0.001 ) { group.wait() }
56
+ assert_wont_timeout( 0.030 ) { group.wait() }
57
+ end
58
+
59
+ it "completes immediatly after a last synchronous barrier" do
60
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
61
+ (1..10).each do
62
+ target_queue.dispatch_async( group:group ) { sleep( 0.001 ) }
63
+ end
64
+
65
+ target_queue.dispatch_barrier_async( group:group ) { sleep( 0.001 ) }
66
+ (1..10).each do
67
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
68
+ end
69
+
70
+ assert_must_timeout( 0.001 ) { group.wait() }
71
+ target_queue.dispatch_barrier_sync( group:group ) { sleep( 0.001 ) }
72
+ assert_wont_timeout( 0.001 ) { group.wait() }
73
+ end
74
+ end # DispatchGroup with ConcurrentQueue
75
+ end # module DispatchQueue
@@ -0,0 +1,33 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_group_serial_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
+ require 'timeout'
13
+
14
+ module DispatchQueue
15
+ describe "DispatchGroup with SerialQueue" do
16
+
17
+ let( :target_queue ) { SerialQueue.new }
18
+ let( :group ) { DispatchGroup.new }
19
+
20
+ it "leaves group when task completes, with single task on idle queue " do
21
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
22
+ assert_must_timeout( 0.001 ) { group.wait() }
23
+ assert_wont_timeout( 0.002 ) { group.wait() }
24
+ end
25
+
26
+ it "leaves group when task completes, with enquened tasks" do
27
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
28
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
29
+ assert_must_timeout( 0.001 ) { group.wait() }
30
+ assert_wont_timeout( 0.005 ) { group.wait() }
31
+ end
32
+ end # DispatchGroup with SerialQueue
33
+ end # module DispatchQueue
@@ -0,0 +1,34 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_group_thread_pool_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
+ require 'timeout'
13
+
14
+ module DispatchQueue
15
+ describe "DispatchGroup with ThreadPoolQueue" do
16
+ let( :target_queue ) { Dispatch.default_queue }
17
+ let( :group ) { DispatchGroup.new }
18
+
19
+ it "leaves group when task completes, with single task on idle queue " do
20
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
21
+ assert_must_timeout( 0.001 ) { group.wait() }
22
+ assert_wont_timeout( 0.002 ) { group.wait() }
23
+ end
24
+
25
+ it "leaves group when task completes, with enquened tasks" do
26
+ (1..10).each do
27
+ target_queue.dispatch_async( group:group ) { sleep( 0.002 ) }
28
+ end
29
+
30
+ assert_must_timeout( 0.001 ) { group.wait() }
31
+ assert_wont_timeout( 0.020 ) { group.wait() }
32
+ end
33
+ end # DispatchGroup with ThreadPoolQueue
34
+ end # module DispatchQueue
@@ -0,0 +1,58 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_heap.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 Heap do
15
+
16
+ subject { Heap.new }
17
+
18
+ def push( heap, *elements )
19
+ elements.each { |e| heap.push( e ) }
20
+ end
21
+
22
+ def drain( heap )
23
+ elements = []
24
+ elements.push( heap.pop() ) while !heap.empty?
25
+ elements
26
+ end
27
+
28
+ it "can be initialized with no argument" do
29
+ subject.must_be_instance_of Heap
30
+ end
31
+
32
+ it "is empty when newly created" do
33
+ subject.size.must_equal 0
34
+ subject.count.must_equal 0
35
+ subject.length.must_equal 0
36
+ subject.empty?.must_equal true
37
+ end
38
+
39
+ it "stores pushed elements in an order that preserves heap property" do
40
+ push( subject, 7, 1, 6, 2, 5, 3, 4 )
41
+ subject.size.must_equal 7
42
+ subject.empty?.must_equal false
43
+ subject.elements.must_equal( [1, 2, 3, 7, 5, 6, 4] )
44
+ end
45
+
46
+ it "pops elements in sorted order" do
47
+ push( subject, 7, 1, 6, 2, 5, 3, 4 )
48
+ drain( subject ).must_equal [1, 2, 3, 4, 5, 6, 7]
49
+ end
50
+
51
+ it "pops elements in sorted order" do
52
+ count = 100
53
+ push( subject, *(1..count).to_a.shuffle )
54
+ drain( subject ).must_equal (1..count).to_a
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,77 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_serial_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 SerialQueue 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 { SerialQueue.new }
21
+
22
+ it "can initialize a serial queue" do
23
+ subject.must_be_instance_of SerialQueue
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 serial queues" do
62
+ let( :subject2 ) { SerialQueue.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,63 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_thread_pool_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 ThreadPoolQueue do
15
+
16
+ let( :log ) { [] }
17
+ let( :lock ) { Mutex.new }
18
+ let( :logger ) { Proc.new { |e, d| lock.synchronize { log << [e, d] } } }
19
+ let( :max_threads ) { 2 }
20
+ subject { ThreadPoolQueue.new( max_threads: max_threads,
21
+ debug_trace: logger,
22
+ thread_termination_delay: 0.001 ) }
23
+
24
+ it "can initialize a thread ppol" do
25
+ subject.must_be_instance_of ThreadPoolQueue
26
+ end
27
+
28
+ it "can execute work in the thread pool" do
29
+ (1..10).each do |i|
30
+ subject.dispatch_async do
31
+ sleep(0.001)
32
+ logger.call( :task, { task_id:i, thread:Thread.current } )
33
+ end
34
+ end
35
+ subject.wait_for_all_threads_termination()
36
+
37
+ completed_tasks = Set.new( log.select { |e, d| e == :task }.map { |e, d| d[:task_id] } )
38
+ completed_tasks.must_equal( Set.new( 1..10 ) )
39
+ end
40
+
41
+ it "spawns and terminates threads as needed" do
42
+ (1..200).each do |i|
43
+ subject.dispatch_async do
44
+ sleep(0.001)
45
+ logger.call( :task, { task_id:i, thread:Thread.current } )
46
+ end
47
+ end
48
+
49
+ sleep(0.02)
50
+ subject.max_threads = 8
51
+ sleep(0.01)
52
+ subject.max_threads = 2
53
+
54
+ subject.wait_for_all_threads_termination()
55
+
56
+ thread_count = 0
57
+ thread_count_list = log.map { |e, d| thread_count = d[:thread_count] || thread_count }
58
+ major_thread_counts = thread_count_list.chunk { |n| n }.map { |cnt, e| [cnt, e.count] }.select { |t,e| e > 10 }.map { |t,e| t }
59
+ major_thread_counts.must_equal( [2, 8, 2] )
60
+ end
61
+
62
+ end
63
+ end