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,33 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/condition_variable_pool.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ module ConditionVariablePool
12
+ class << self
13
+
14
+ @@mutex = Mutex.new
15
+ @@pool = []
16
+
17
+ def acquire()
18
+ @@mutex.synchronize do
19
+ return @@pool.shift if !@@pool.empty?
20
+ end
21
+
22
+ return Mutex.new, ConditionVariable.new
23
+ end
24
+
25
+ def release( mutex, condition )
26
+ @@mutex.synchronize do
27
+ @@pool << [mutex, condition]
28
+ end
29
+ end
30
+
31
+ end
32
+ end # class ConditionVariablePool
33
+ end # module DispatchQueue
@@ -0,0 +1,41 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/continuation.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class Continuation
12
+ attr_reader :barrier
13
+ attr_reader :eta
14
+
15
+ def initialize( target_queue:nil, group:nil, barrier:false, eta:nil, &task )
16
+ @task = task
17
+ @target_queue = target_queue
18
+ @group = group
19
+ @barrier = barrier
20
+ @eta = eta
21
+ end
22
+
23
+ def run( default_target_queue:nil, override_target_queue:nil )
24
+ queue = override_target_queue || @target_queue || default_target_queue
25
+ if queue
26
+ if @barrier
27
+ queue.dispatch_barrier_async( group:@group, &@task )
28
+ else
29
+ queue.dispatch_async( group:@group, &@task )
30
+ end
31
+ @group.leave() if @group
32
+ else
33
+ begin
34
+ @task.call() if @task
35
+ ensure
36
+ @group.leave() if @group
37
+ end
38
+ end
39
+ end
40
+ end # class Continuation
41
+ end # module DispatchQueue
@@ -0,0 +1,88 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/heap.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class Heap
12
+
13
+ def initialize( &compare )
14
+ @heap = []
15
+ @compare = compare
16
+ end
17
+
18
+ def push( elt )
19
+ @heap.push( elt )
20
+ _sift_up( @heap.count - 1 )
21
+ end
22
+
23
+ def pop()
24
+ head = @heap.first
25
+ tail = @heap.pop()
26
+ @heap[0] = tail if !@heap.empty?
27
+ _sift_down( 0 )
28
+ head
29
+ end
30
+
31
+ def head()
32
+ @heap.first
33
+ end
34
+
35
+ def count
36
+ @heap.count
37
+ end
38
+
39
+ alias_method :size, :count
40
+ alias_method :length, :count
41
+
42
+ def empty?
43
+ @heap.empty?
44
+ end
45
+
46
+ def elements
47
+ @heap
48
+ end
49
+
50
+ private
51
+ def _ordered?( i, j )
52
+ a = @heap[i]
53
+ b = @heap[j]
54
+ return @compare.call( a, b ) if @compare
55
+ return a < b
56
+ end
57
+
58
+ def _swap( i, j )
59
+ @heap[i], @heap[j] = @heap[j], @heap[i]
60
+ end
61
+
62
+ def _sift_up( i )
63
+ loop do
64
+ parent = (i-1) / 2
65
+ break if parent < 0
66
+ break if _ordered?( parent, i )
67
+ _swap( parent, i )
68
+ i = parent
69
+ end
70
+ end
71
+
72
+ def _sift_down( i )
73
+ size = @heap.size
74
+ loop do
75
+ left = i*2 + 1
76
+ right = left + 1
77
+ largest = i
78
+
79
+ largest = left if left < size && _ordered?( left, largest )
80
+ largest = right if right < size && _ordered?( right, largest )
81
+ break if largest == i
82
+ _swap( i, largest )
83
+ i = largest
84
+ end
85
+ end
86
+
87
+ end # class Heap
88
+ end # module DispatchQueue
@@ -0,0 +1,127 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/thread_pool_queue.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class ThreadPoolQueue
12
+
13
+ def initialize( max_threads:nil, debug_trace:nil, thread_termination_delay:0.01 )
14
+ @max_threads = max_threads || Dispatch.ncpu()
15
+ @debug_trace = debug_trace
16
+ @thread_termination_delay = thread_termination_delay
17
+
18
+ @mutex = Mutex.new
19
+ @condition = ConditionVariable.new
20
+ @tasks = []
21
+ @worker_threads = Set.new
22
+ @idle_count = 0
23
+ end
24
+
25
+ def dispatch_async( group:nil, &task )
26
+ group.enter() if group
27
+ @mutex.synchronize do
28
+ @tasks << Continuation.new( group:group, &task )
29
+ @condition.signal()
30
+ _sync_try_spwan_more_threads()
31
+ end
32
+ self
33
+ end
34
+
35
+ alias_method :dispatch_barrier_async, :dispatch_async
36
+
37
+ include DispatchSyncImpl
38
+ include DispatchAfterImpl
39
+
40
+
41
+
42
+ def max_threads
43
+ @mutex.synchronize do
44
+ @max_threads
45
+ end
46
+ end
47
+
48
+ def max_threads=( new_value )
49
+ @mutex.synchronize do
50
+ @max_threads = new_value
51
+ _sync_try_spwan_more_threads()
52
+ end
53
+ end
54
+
55
+ def wait_for_all_threads_termination
56
+ loop do
57
+ sleep 0.001
58
+ break if @tasks.empty? && @worker_threads.empty?
59
+ end
60
+ end
61
+
62
+
63
+ private
64
+ def _worker_main()
65
+ Thread.current[:current_queue] = self
66
+
67
+ begin
68
+ loop do
69
+ task = _pop_task()
70
+ break if task.nil?
71
+ task.run()
72
+ end
73
+ ensure
74
+ @mutex.synchronize do
75
+ @worker_threads.delete( Thread.current )
76
+ @debug_trace.call( :terminated, { thread: Thread.current,
77
+ thread_count: @worker_threads.count,
78
+ idle_count: @idle_count,
79
+ task_count: @tasks.count } ) if @debug_trace
80
+ end
81
+ end
82
+ end
83
+
84
+ def _pop_task()
85
+ @mutex.synchronize do
86
+ return nil if @worker_threads.count > @max_threads
87
+ _sync_wait_for_item()
88
+ task = @tasks.shift();
89
+ _sync_try_spwan_more_threads() if task
90
+ task
91
+ end
92
+ end
93
+
94
+
95
+ #
96
+ # _sync_xxx() methods assume that the mutex is already owned
97
+ #
98
+
99
+ def _sync_try_spwan_more_threads()
100
+ return if @worker_threads.count >= @max_threads
101
+ return if @idle_count > 0
102
+ return if @tasks.empty?
103
+
104
+ thread = _sync_spawn_worker_thread()
105
+ @debug_trace.call( :spawned, { thread: thread,
106
+ from_thread: Thread.current,
107
+ thread_count: @worker_threads.count,
108
+ idle_count: @idle_count,
109
+ task_count: @tasks.count } ) if @debug_trace
110
+ end
111
+
112
+ def _sync_spawn_worker_thread()
113
+ thread = Thread.new { _worker_main() }
114
+ @worker_threads.add( thread )
115
+ thread
116
+ end
117
+
118
+ def _sync_wait_for_item()
119
+ if @tasks.empty?
120
+ @idle_count += 1
121
+ @condition.wait( @mutex, @thread_termination_delay )
122
+ @idle_count -= 1
123
+ end
124
+ end
125
+
126
+ end # class ThreadPoolQueue
127
+ end # module DispatchQueue
@@ -0,0 +1,62 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/thread_queue.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class ThreadQueue
12
+ def initialize( thread_termination_delay:0.01 )
13
+ @thread_termination_delay = thread_termination_delay
14
+
15
+ @mutex = Mutex.new
16
+ @condition = ConditionVariable.new
17
+ @tasks = []
18
+ @thread = nil
19
+ end
20
+
21
+ def dispatch_async( group:nil, &task )
22
+ group.enter() if group
23
+ @mutex.synchronize do
24
+ resume = @tasks.empty?
25
+ @tasks << Continuation.new( group:group, &task )
26
+ _sync_spawn_or_wakeup_thread() if resume
27
+ end
28
+ self
29
+ end
30
+
31
+ alias_method :dispatch_barrier_async, :dispatch_async
32
+
33
+ include DispatchSyncImpl
34
+ include DispatchAfterImpl
35
+
36
+
37
+ private
38
+ def _thread_main
39
+ Thread.current[:current_queue] = self
40
+
41
+ @mutex.synchronize do
42
+ loop do
43
+ @tasks.shift.run() if !@tasks.empty?
44
+
45
+ next if !@tasks.empty?
46
+ @condition.wait( @mutex, @thread_termination_delay )
47
+
48
+ if @tasks.empty?
49
+ @thread = nil
50
+ break
51
+ end
52
+ end # loop
53
+ end
54
+ end
55
+
56
+ def _sync_spawn_or_wakeup_thread()
57
+ @thread = Thread.new { _thread_main() } if @thread.nil?
58
+ @condition.signal()
59
+ end
60
+
61
+ end # class ThreadQueue
62
+ end # module DispatchQueue
@@ -0,0 +1,71 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/internal/timer_pool.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ class TimerPool
12
+
13
+ def self.default_pool
14
+ @@default_pool
15
+ end
16
+
17
+ def initialize()
18
+ @mutex = Mutex.new
19
+ @condition = ConditionVariable.new
20
+ @heap = Heap.new { |a,b| a.eta < b.eta }
21
+ @scheduled_eta = nil
22
+ @thread = nil
23
+ end
24
+
25
+ def dispatch_after( eta, group:nil, target_queue:nil, &task )
26
+ group.enter() if group
27
+ target_queue ||= Dispatch.default_queue
28
+ eta = Time.now + eta if !(Time === eta)
29
+ continuation = Continuation.new( target_queue:target_queue, group:group, eta:eta, &task )
30
+
31
+ @mutex.synchronize do
32
+ @heap.push( continuation )
33
+ if @scheduled_eta.nil? || eta < @scheduled_eta
34
+ @thread = Thread.new { _thread_main() } if @thread.nil?
35
+ @condition.signal()
36
+ end
37
+ end
38
+ target_queue
39
+ end
40
+
41
+
42
+
43
+ private
44
+ def _thread_main
45
+ @mutex.synchronize do
46
+ loop do
47
+ _fire_expired_timers()
48
+ wait_time = @heap.head.eta - Time.now if !@heap.empty?
49
+ next if wait_time && wait_time < 0
50
+
51
+ idle = @heap.empty?
52
+ @condition.wait( @mutex, wait_time || 0.01 )
53
+ if idle && @heap.empty?
54
+ @thread = nil
55
+ break
56
+ end
57
+ end # loop
58
+ end
59
+ end
60
+
61
+ def _fire_expired_timers
62
+ while !@heap.empty? && @heap.head.eta < Time.now do
63
+ continuation = @heap.pop
64
+ continuation.run()
65
+ end
66
+ end
67
+
68
+ @@default_pool = self.new
69
+
70
+ end # class TimerPool
71
+ end # module DispatchQueue
@@ -0,0 +1,18 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/mixins/dispatch_after_impl.rb
4
+ # PROJECT : DispatchQueue
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ module DispatchAfterImpl
12
+
13
+ def dispatch_after( eta, group:nil, &task )
14
+ TimerPool.default_pool.dispatch_after( eta, group:group, target_queue:self, &task )
15
+ end
16
+
17
+ end # class DispatchAfterImpl
18
+ end # module DispatchQueue