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