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,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