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