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.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +98 -0
- data/dispatch_queue_rb.gemspec +43 -0
- data/lib/dispatch_queue_rb.rb +32 -0
- data/lib/dispatch_queue_rb/concurrent_queue.rb +116 -0
- data/lib/dispatch_queue_rb/dispatch.rb +66 -0
- data/lib/dispatch_queue_rb/dispatch_group.rb +72 -0
- data/lib/dispatch_queue_rb/internal/condition_variable_pool.rb +33 -0
- data/lib/dispatch_queue_rb/internal/continuation.rb +41 -0
- data/lib/dispatch_queue_rb/internal/heap.rb +88 -0
- data/lib/dispatch_queue_rb/internal/thread_pool_queue.rb +127 -0
- data/lib/dispatch_queue_rb/internal/thread_queue.rb +62 -0
- data/lib/dispatch_queue_rb/internal/timer_pool.rb +71 -0
- data/lib/dispatch_queue_rb/mixins/dispatch_after_impl.rb +18 -0
- data/lib/dispatch_queue_rb/mixins/dispatch_sync_impl.rb +42 -0
- data/lib/dispatch_queue_rb/serial_queue.rb +77 -0
- data/lib/dispatch_queue_rb/version.rb +12 -0
- data/rakefile.rb +110 -0
- data/test/_test_env.rb +52 -0
- data/test/test_concurrent_queue.rb +90 -0
- data/test/test_condition_variable_pool.rb +41 -0
- data/test/test_continuation.rb +23 -0
- data/test/test_dispatch.rb +91 -0
- data/test/test_dispatch_group.rb +59 -0
- data/test/test_group_concurrent_queue.rb +75 -0
- data/test/test_group_serial_queue.rb +33 -0
- data/test/test_group_thread_pool_queue.rb +34 -0
- data/test/test_heap.rb +58 -0
- data/test/test_serial_queue.rb +77 -0
- data/test/test_thread_pool_queue.rb +63 -0
- data/test/test_thread_queue.rb +77 -0
- data/test/test_timer_pool.rb +124 -0
- data/test/test_version.rb +155 -0
- 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
|