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,42 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/mixins/dispatch_sync_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 DispatchSyncImpl
12
+
13
+ def dispatch_sync( group:nil, &task )
14
+ mutex, condition = ConditionVariablePool.acquire()
15
+ result = nil
16
+ mutex.synchronize do
17
+ dispatch_async( group:group ) do
18
+ result = task.call()
19
+ mutex.synchronize { condition.signal() }
20
+ end
21
+ condition.wait( mutex )
22
+ end
23
+ ConditionVariablePool.release( mutex, condition )
24
+ result
25
+ end
26
+
27
+ def dispatch_barrier_sync( group:nil, &task )
28
+ mutex, condition = ConditionVariablePool.acquire()
29
+ result = nil
30
+ mutex.synchronize do
31
+ dispatch_barrier_async( group:group ) do
32
+ result = task.call()
33
+ mutex.synchronize { condition.signal() }
34
+ end
35
+ condition.wait( mutex )
36
+ end
37
+ ConditionVariablePool.release( mutex, condition )
38
+ result
39
+ end
40
+
41
+ end # class DispatchSyncImpl
42
+ end # module DispatchQueue
@@ -0,0 +1,77 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/serial_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 SerialQueue
12
+
13
+ def initialize( parent_queue: nil )
14
+ @mutex = Mutex.new
15
+ @condition = ConditionVariable.new
16
+ @task_list = []
17
+ @active = false
18
+ @parent_queue = parent_queue || Dispatch.default_queue
19
+ end
20
+
21
+ def dispatch_async( group:nil, &task )
22
+ group.enter() if group
23
+ continuation = Continuation.new( target_queue:@parent_queue, group:group ) do
24
+ _run_task( task )
25
+ end
26
+
27
+ continuation.run() if _try_activate_or_enqueue( continuation )
28
+ self
29
+ end
30
+
31
+ alias_method :dispatch_barrier_async, :dispatch_async
32
+
33
+ include DispatchSyncImpl
34
+ include DispatchAfterImpl
35
+
36
+
37
+
38
+ private
39
+ def _try_activate_or_enqueue( continuation )
40
+ @mutex.synchronize do
41
+ if (!@active)
42
+ @active = true
43
+ return true
44
+ else
45
+ @task_list << continuation
46
+ return false
47
+ end
48
+ end
49
+ end
50
+
51
+ def _run_task( task )
52
+ previous_queue = Thread.current[:current_queue]
53
+ Thread.current[:current_queue] = self
54
+
55
+ begin
56
+ task.call()
57
+ ensure
58
+ Thread.current[:current_queue] = previous_queue
59
+ _dispatch_next_task()
60
+ end
61
+ end
62
+
63
+ def _dispatch_next_task()
64
+ continuation = _suspend_or_next_task()
65
+ continuation.run() if continuation
66
+ end
67
+
68
+ def _suspend_or_next_task()
69
+ @mutex.synchronize do
70
+ return @task_list.shift if !@task_list.empty?
71
+ @active = false
72
+ return nil
73
+ end
74
+ end
75
+
76
+ end # class SerialQueue
77
+ end # module DispatchQueue
@@ -0,0 +1,12 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : lib/dispatch_queue_rb/version.rb
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ module DispatchQueue
11
+ VERSION = '1.0.0'
12
+ end
@@ -0,0 +1,110 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : rakefile.rb
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+
11
+ require 'bundler/gem_tasks'
12
+ require 'rake/testtask'
13
+
14
+ task default: [:test, :build]
15
+
16
+ Rake::TestTask.new do |t|
17
+ t.libs << '.' << 'test'
18
+ t.test_files = FileList['test/**/test_*.rb']
19
+ t.verbose = false
20
+ end
21
+
22
+
23
+
24
+ # ----------------------------------------------------------------------------
25
+ # Definitions to help formating 'rake watch' results
26
+ # ----------------------------------------------------------------------------
27
+
28
+ TERM_WIDTH = `tput cols`.to_i || 80
29
+
30
+ def tty_red(str); "\e[31m#{str}\e[0m" end
31
+ def tty_green(str); "\e[32m#{str}\e[0m" end
32
+ def tty_blink(str); "\e[5m#{str}\e[25m" end
33
+ def tty_reverse_color(str); "\e[7m#{str}\e[27m" end
34
+
35
+ def print_separator( success = true )
36
+ if success
37
+ puts tty_green( "-" * TERM_WIDTH )
38
+ else
39
+ puts tty_reverse_color(tty_red( "-" * TERM_WIDTH ))
40
+ end
41
+ end
42
+
43
+
44
+
45
+ # ----------------------------------------------------------------------------
46
+ # Definition of watch task, that monitors the project folder for any relevant
47
+ # file change and runs the unit test of the project.
48
+ # ----------------------------------------------------------------------------
49
+
50
+ begin
51
+ require 'watch'
52
+
53
+ desc 'Run unit tests everytime a source or test file is changed'
54
+ task :watch do
55
+ Watch.new( '**/*.rb' ) do
56
+ success = system "clear && rake test"
57
+ print_separator( success )
58
+ end
59
+ end
60
+
61
+ rescue LoadError
62
+
63
+ desc 'Run unit tests everytime a source or test file is changed'
64
+ task :watch do
65
+ puts
66
+ puts "'rake watch' requires the watch gem to be available"
67
+ puts
68
+ puts "To install:"
69
+ puts " gem install watch"
70
+ puts " or "
71
+ puts " sudo gem install watch"
72
+ puts
73
+ fail
74
+ end
75
+ end
76
+
77
+
78
+
79
+ # ----------------------------------------------------------------------------
80
+ # Definition of add_class[class_name] task, that uses folder_template to add
81
+ # a new class to a ruvy project
82
+ # ----------------------------------------------------------------------------
83
+
84
+ PROJECT_CONTEXT = {
85
+ project_name: "dispatch_queue_rb",
86
+ project_namespace: "DispatchQueue",
87
+ copyright_owner: "Marc-Antoine Argenton",
88
+ copyright_year: "2016",
89
+ }
90
+
91
+ begin
92
+ require 'folder_template'
93
+
94
+ task :add_class, :class_name do |t, args|
95
+ context = PROJECT_CONTEXT.merge( name:args[:class_name])
96
+ FolderTemplate::SetupFolderCmd.run( '.', 'rubyclass', context )
97
+ end
98
+ rescue LoadError
99
+ task :add_class, :class_name do |t, args|
100
+ puts
101
+ puts "'rake add_class[class_name]' task requires the folder_template gem to be available"
102
+ puts
103
+ puts "To install:"
104
+ puts " gem install folder_template"
105
+ puts " or "
106
+ puts " sudo gem install folder_template"
107
+ puts
108
+ fail
109
+ end
110
+ end
@@ -0,0 +1,52 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/_test_env.rb
4
+ # PROJECT : DispatchQueueRb
5
+ # DESCRIPTION :
6
+ #
7
+ # Copyright (c) 2016, Marc-Antoine Argenton. All rights reserved.
8
+ # =============================================================================
9
+
10
+ require 'minitest/autorun'
11
+ require 'minitest/reporters'
12
+ require 'fileutils'
13
+ require 'pp'
14
+ require 'rr'
15
+ require 'timeout'
16
+
17
+ require 'lib/dispatch_queue_rb.rb'
18
+
19
+ # Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new]
20
+ # Minitest.backtrace_filter
21
+ # Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new( color:true ), Minitest::Reporters::JUnitReporter.new]
22
+
23
+
24
+ Thread.abort_on_exception = true
25
+
26
+ module Minitest::Assertions
27
+ def assert_must_timeout( timeout_delay = 0.001 )
28
+ success = false
29
+ begin
30
+ timeout( timeout_delay ) do
31
+ yield
32
+ end
33
+ rescue Timeout::Error => e
34
+ success = true
35
+ end
36
+
37
+ assert success, "Expected operation to not complete within #{timeout_delay}s, but did"
38
+ end
39
+
40
+ def assert_wont_timeout( timeout_delay = 0.001 )
41
+ success = true
42
+ begin
43
+ timeout( timeout_delay ) do
44
+ yield
45
+ end
46
+ rescue Timeout::Error => e
47
+ success = false
48
+ end
49
+
50
+ assert success, "Expected operation to complete within #{timeout_delay}s, but did not"
51
+ end
52
+ end
@@ -0,0 +1,90 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_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
+
13
+ module DispatchQueue
14
+ describe ConcurrentQueue do
15
+
16
+ let( :task_count ) { 10 }
17
+ let( :log ) { [] }
18
+ let( :lock ) { Mutex.new }
19
+ let( :logger ) { Proc.new { |e, d| lock.synchronize { log << [e, d] } } }
20
+ subject { ConcurrentQueue.new }
21
+
22
+ it "can initialize a serial queue" do
23
+ subject.must_be_instance_of ConcurrentQueue
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 concurrently" 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
+ running_count = 0
38
+ parallel_task_count = log.map do |e,d|
39
+ running_count += 1 if e == :begin_task
40
+ running_count -= 1 if e == :end_task
41
+ running_count
42
+ end
43
+
44
+ parallel_task_count.max.must_be :>, 1, parallel_task_count
45
+ end
46
+
47
+ it "preserves ordering arround barrier" do
48
+ (1..task_count).each { |i| subject.dispatch_async { task( "a#{i}".to_sym ) } }
49
+ subject.dispatch_barrier_async { task( "b0".to_sym ) }
50
+ (1..task_count).each { |i| subject.dispatch_async { task( "c#{i}".to_sym ) } }
51
+ subject.dispatch_barrier_async { task( "d0".to_sym ) }
52
+
53
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
54
+
55
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
56
+ task_chunks = task_id_list.chunk { |e| e.to_s[0] }.map { |c, l| l }
57
+ task_chunks.count.must_be :==, 4, task_chunks
58
+ end
59
+
60
+ it "resumes execution after synchronous barrier" do
61
+ (1..task_count).each { |i| subject.dispatch_async { task( "a#{i}".to_sym ) } }
62
+ subject.dispatch_barrier_sync { task( "b0".to_sym ) }
63
+ (1..task_count).each { |i| subject.dispatch_async { task( "c#{i}".to_sym ) } }
64
+ subject.dispatch_barrier_sync { task( "d0".to_sym ) }
65
+
66
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
67
+
68
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
69
+ task_chunks = task_id_list.chunk { |e| e.to_s[0] }.map { |c, l| l }
70
+ task_chunks.count.must_be :==, 4, task_chunks
71
+ end
72
+ end
73
+
74
+ describe "with multiple concurrent queues" do
75
+ let( :subject2 ) { ConcurrentQueue.new }
76
+
77
+ it "interleaves barrier tasks from different queues" do
78
+ (1..task_count).each { |i| subject.dispatch_barrier_async { task( "a#{i}".to_sym ) } }
79
+ (1..task_count).each { |i| subject2.dispatch_barrier_async { task( "b#{i}".to_sym ) } }
80
+ subject.dispatch_barrier_sync {} # wait for all previous tasks to complete
81
+ subject2.dispatch_barrier_sync {} # wait for all previous tasks to complete
82
+
83
+ task_id_list = log.select { |e,d| e == :begin_task }.map { |e,d| d[:task_id] }
84
+ task_chunks = task_id_list.chunk { |e| e.to_s[0] }.map { |c, l| l }
85
+ task_chunks.count.must_be :>, 3, task_chunks
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,41 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_condition_variable_pool.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 ConditionVariablePool do
15
+
16
+ describe "acquire" do
17
+ it "returns a pair of mutex and condition variable" do
18
+ mutex, condition = ConditionVariablePool.acquire()
19
+ mutex.must_be_instance_of Mutex
20
+ condition.must_be_instance_of ConditionVariable
21
+ end
22
+ end
23
+
24
+ describe "release" do
25
+ it "releases mutex and condition variable" do
26
+ mutex, condition = ConditionVariablePool.acquire()
27
+ ConditionVariablePool.release( mutex, condition )
28
+ end
29
+
30
+ it "recycles released mutex and condition variable" do
31
+ mutex, condition = ConditionVariablePool.acquire()
32
+ ConditionVariablePool.release( mutex, condition )
33
+ mutex2, condition2 = ConditionVariablePool.acquire()
34
+
35
+ mutex2.must_be_same_as mutex
36
+ condition2.must_be_same_as condition
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # =============================================================================
2
+ #
3
+ # MODULE : test/test_continuation.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 Continuation do
15
+
16
+ subject { Continuation.new }
17
+
18
+ it "passes this one" do
19
+ subject.must_be_instance_of Continuation
20
+ end
21
+
22
+ end
23
+ end