eldritch 0.1.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 421afdae683a86e47b2138e1c5b22ff833a7ef7a
4
- data.tar.gz: e364c1a1030cf95dc5a3aa38efac6cf5a44111e2
3
+ metadata.gz: 1cbab97dd7492c7c19829d608f555e6cd1660901
4
+ data.tar.gz: a05f7a551bc3c73736f86846b2fced0f51000e67
5
5
  SHA512:
6
- metadata.gz: 0b558a3aa5e75a0a84f7d58dc292ea9afb49a8991442056d0e8afccbbd321d964d825d3fbab53d2e721a90fbee6722342f360948cf1440e82c9b674de5eb9e97
7
- data.tar.gz: b7faec1e5ad653d78669e60a6ba0e11f4c9e16f029fd129b88575da4193edf5af1773ad98d7881d85d05d372bee941b7ed6fa20fded7f277be8b3a263293f569
6
+ metadata.gz: 679b8be6668f63dbd2b688e94b64f30f58ec858d0d6179ddc06ccc3e434ddd4973214bf7af41846849d970888494d2c8788d07ae11e859f46949416ebedb1351
7
+ data.tar.gz: 4478f50cf4c09996245b2407007287a03368731818bdaeb466e7cd6ee6602ff321e6fc7083023f622deee32fe521e6d3a16d766809455f31fda2eef8afd7f452
@@ -1,3 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.1.0
3
4
  - 2.1.1
data/README.md CHANGED
@@ -3,9 +3,14 @@ Eldritch
3
3
 
4
4
  _The dark arts of concurrent programming._
5
5
 
6
- **This gem is in development and in no way shape or form production ready.**
6
+ A DSL that adds parallel programming constructs to make your life a little easier.
7
7
 
8
- A ruby gem that adds parallel programming constructs to make your life a little easier.
8
+ Code quality
9
+ ------------
10
+
11
+ [![Build Status](http://travis-ci.org/beraboris/eldritch.svg?branch=master)](http://travis-ci.org/beraboris/eldritch)
12
+ [![Coverage Status](http://coveralls.io/repos/beraboris/eldritch/badge.png)](http://coveralls.io/r/beraboris/eldritch)
13
+ [![Code Climate](http://codeclimate.com/github/beraboris/eldritch.png)](http://codeclimate.com/github/beraboris/eldritch)
9
14
 
10
15
  Features
11
16
  --------
@@ -60,16 +65,16 @@ end
60
65
  res = 2 + task.value # waits for the task to finish
61
66
  ```
62
67
 
63
- ### together
68
+ ### together blocks
64
69
 
65
- Together blocks are used to control all async blocks and methods within them as a group. Right now, the together block
66
- waits for all async calls be de done before exiting.
70
+ Together blocks are used to control all async blocks and methods within them as a group. Before exiting, together blocks
71
+ wait for all their async calls to be done before returning.
67
72
 
68
73
  ```ruby
69
74
  require 'eldritch'
70
75
 
71
76
  together do
72
- 1000.each do
77
+ 1000.times do
73
78
  async do
74
79
  # do some work
75
80
  end
@@ -78,6 +83,22 @@ end
78
83
  # all 1000 tasks are done
79
84
  ```
80
85
 
86
+ These blocks can also take an argument. This argument is a group that can be used to control the async calls in the
87
+ block. See the documentation for Eldritch::Group for more information.
88
+
89
+ ```ruby
90
+ require 'eldritch'
91
+
92
+ together do |group|
93
+ 5.times do
94
+ async do
95
+ # do something
96
+ group.interrupt if some_condition # stops all other tasks
97
+ end
98
+ end
99
+ end
100
+ ```
101
+
81
102
  Installation
82
103
  ------------
83
104
 
@@ -92,12 +113,3 @@ And then execute:
92
113
  Or install it yourself as:
93
114
 
94
115
  $ gem install eldritch
95
-
96
- Code quality
97
- ------------
98
-
99
- [![Build Status](http://travis-ci.org/beraboris/eldritch.svg?branch=master)](http://travis-ci.org/beraboris/eldritch)
100
-
101
- [![Coverage Status](http://coveralls.io/repos/beraboris/eldritch/badge.png)](http://coveralls.io/r/beraboris/eldritch)
102
-
103
- [![Code Climate](http://codeclimate.com/github/beraboris/eldritch.png)](http://codeclimate.com/github/beraboris/eldritch)
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'yard'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
6
+ YARD::Rake::YardocTask.new(:doc)
5
7
 
6
8
  task :default => :spec
@@ -8,11 +8,14 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Eldritch::VERSION
9
9
  spec.authors = ['Boris Bera', 'François Genois']
10
10
  spec.email = %w(bboris@rsoft.ca frankgenerated@gmail.com)
11
- spec.summary = %q{Adds tools to make parallelism easier.}
11
+ spec.summary = %q{DSL that adds concurrent programming concepts to make your life easier.}
12
12
  spec.description = %q{Adds support for async methods and async blocks. Adds a together block that allows async methods/blocks to be controlled as a group.}
13
13
  spec.homepage = 'https://github.com/beraboris/eldritch'
14
14
  spec.license = 'MIT'
15
15
 
16
+ # need refinements
17
+ spec.required_ruby_version = '>= 2.1'
18
+
16
19
  spec.files = `git ls-files -z`.split("\x0")
17
20
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
@@ -21,4 +24,5 @@ Gem::Specification.new do |spec|
21
24
  spec.add_development_dependency 'bundler', '~> 1.5'
22
25
  spec.add_development_dependency 'rake'
23
26
  spec.add_development_dependency 'rspec', '~> 2.14'
27
+ spec.add_development_dependency 'yard', '~> 0.8.7.4'
24
28
  end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'eldritch'
3
+
4
+ def print_matrix(matrix)
5
+ matrix.each {|line| puts line.inspect}
6
+ end
7
+
8
+ a = [[1, 2, 3],
9
+ [4, 5, 6]]
10
+
11
+ b = [[5, 6],
12
+ [7, 8],
13
+ [9, 10]]
14
+ b_t = b.transpose
15
+
16
+ puts 'matrix A:'
17
+ print_matrix(a)
18
+
19
+ puts 'matrix B:'
20
+ print_matrix(b)
21
+
22
+ c = [[0, 0],
23
+ [0, 0]]
24
+
25
+ together do
26
+ a.each_with_index do |row, y|
27
+ b_t.each_with_index do |col, x|
28
+ async do
29
+ # scalar product
30
+ c[y][x] = row.zip(col).map{|i, j| i*j}.reduce(:+)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ puts 'A x B = '
37
+ print_matrix(c)
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ require 'eldritch'
3
+ require 'digest/md5'
4
+
5
+ if ARGV.size < 2
6
+ puts 'Cracks 4 lowercase letter password hashed using MD5'
7
+ puts
8
+ puts 'usage: password_cracker.rb <threads> <hash>'
9
+ puts ' threads: the number of threads to run'
10
+ puts ' hash: MD5 hash to crack'
11
+ puts
12
+ puts 'example:'
13
+ puts ' password_cracker.rb 4 31d7c3e829be03400641f80b821ef728'
14
+ puts ' prints "butts"'
15
+ exit 1
16
+ end
17
+
18
+ threads = ARGV.shift.to_i
19
+ hash = ARGV.shift
20
+
21
+ # generate all possible 4 lowercase letter passwords
22
+ passwords = ('a'..'z').to_a.repeated_permutation(4).lazy.map &:join
23
+
24
+ together do |group|
25
+ # cut the passwords into slices
26
+ passwords.each_slice(passwords.size/threads) do |slice|
27
+ async do
28
+ slice.each do |password|
29
+ if hash == Digest::MD5.hexdigest(password)
30
+ group.synchronize { puts password }
31
+
32
+ # stop all the other threads
33
+ group.interrupt
34
+ break
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,36 @@
1
1
  module Eldritch
2
+ # Provides DSL for:
3
+ # - {#async async methods}
4
+ # - {#async async blocks}
5
+ # - {#together together blocks}
6
+ # - {#sync sync keyword}
2
7
  module DSL
8
+ using Eldritch::Refinements
9
+
10
+ # Creates an asynchronous method or starts an async block
11
+ #
12
+ # If a block is passed, this will be an async block.
13
+ # Otherwise this method will create an async method.
14
+ #
15
+ # When an async block is called, it will yield the block in a new thread.
16
+ #
17
+ # async do
18
+ # # will run in parallel
19
+ # end
20
+ # #=> <Task>
21
+ #
22
+ # When called, async methods behave exactly like async blocks.
23
+ #
24
+ # async def foo
25
+ # # will run in parallel
26
+ # end
27
+ #
28
+ # foo
29
+ # #=> <Task>
30
+ #
31
+ # @param [Symbol] method the name of the async method.
32
+ # @return [Task] a task representing the async method or block
33
+ # (only for async block and async method call)
3
34
  def async(method=nil, &block)
4
35
  if block
5
36
  async_block(&block)
@@ -8,24 +39,64 @@ module Eldritch
8
39
  end
9
40
  end
10
41
 
42
+ # Allows async methods to be called like synchronous methods
43
+ #
44
+ # sync send_email(42) # send_mail is async
45
+ #
46
+ # @param [Task] task a task returned by {#async}
47
+ # @return whatever the method has returned
11
48
  def sync(task)
12
49
  task.value
13
50
  end
14
51
 
52
+ # Creates a group of async call and blocks
53
+ #
54
+ # When async blocks and calls are inside a together block, they can act as a group.
55
+ #
56
+ # A together block waits for all the async call/blocks that were started within itself to stop before continuing.
57
+ #
58
+ # together do
59
+ # 5.times do
60
+ # async { sleep(1) }
61
+ # end
62
+ # end
63
+ # # waits for all 5 async blocks to complete
64
+ #
65
+ # A together block will also yield a {Group}. This can be used to interact with the other async calls/blocks.
66
+ #
67
+ # together do |group|
68
+ # 5.times do
69
+ # async do
70
+ # # stop everyone else
71
+ # group.interrupt if something?
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ # @yield [Group] group of async blocks/calls
77
+ # @see Group Group class
15
78
  def together
16
- t = Together.new
17
- Thread.current.together = t
18
- yield
19
- t.wait_all
20
- Thread.current.together = nil
79
+ old = Thread.current.group
80
+
81
+ group = Group.new
82
+ Thread.current.group = group
83
+
84
+ yield group
85
+
86
+ group.wait_all
87
+ Thread.current.group = old
21
88
  end
22
89
 
23
90
  private
24
91
 
25
92
  def async_block(&block)
26
- task = Task.new {|t| t.value = block.call }
27
- Thread.current.together << task if Thread.current.together?
28
- task.start
93
+ task = Task.new do |t|
94
+ begin
95
+ t.value = block.call
96
+ rescue InterruptedError
97
+ end
98
+ end
99
+ Thread.current.group << task
29
100
  task
30
101
  end
31
102
 
@@ -0,0 +1,77 @@
1
+ require 'thread'
2
+
3
+ module Eldritch
4
+ # Represents a group of {Task tasks} or {DSL#async async calls/block}.
5
+ # It is used to act upon all the tasks in the group.
6
+ class Group
7
+ using Eldritch::Refinements
8
+
9
+ def initialize
10
+ @tasks = []
11
+ @mutex = Mutex.new
12
+ @accept = true
13
+ end
14
+
15
+ # @return [Array<Task>] the other async calls/blocks in the group
16
+ def others
17
+ @tasks - [Thread.current.task]
18
+ end
19
+
20
+ def <<(task)
21
+ accept = nil
22
+
23
+ @mutex.synchronize do
24
+ # copy accept for the task.start condition
25
+ accept = @accept
26
+ @tasks << task if accept
27
+ end
28
+
29
+ task.start if accept
30
+ end
31
+
32
+ # Yields the block in mutual exclusion with all the async calls/tasks
33
+ #
34
+ # @yield
35
+ def synchronize(&block)
36
+ @mutex.synchronize { block.call }
37
+ end
38
+
39
+ def wait_all
40
+ @tasks.each {|t| t.wait}
41
+ end
42
+
43
+ # Aborts the other async calls/blocks in the group
44
+ #
45
+ # *Warning*: This call will directly kill underlying threads. This isn't very safe.
46
+ #
47
+ # @see Task#abort
48
+ def abort
49
+ @mutex.synchronize do
50
+ @accept = false
51
+ others.each &:abort
52
+ end
53
+ end
54
+
55
+ # Interrupts the other async calls/blocks in the group
56
+ #
57
+ # Interruptions are done using exceptions that can be caught by the async calls/blocks to perform cleanup.
58
+ #
59
+ # @see Task#interrupt
60
+ def interrupt
61
+ @mutex.synchronize do
62
+ @accept = false
63
+ others.each &:interrupt
64
+ end
65
+ end
66
+ end
67
+
68
+ class NilGroup
69
+ def <<(task)
70
+ task.start
71
+ end
72
+
73
+ def nil?
74
+ true
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,4 @@
1
+ module Eldritch
2
+ class InterruptedError < RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,16 @@
1
+ module Eldritch
2
+ module Refinements
3
+ refine Thread do
4
+ attr_writer :group
5
+ attr_accessor :task
6
+
7
+ def group
8
+ @group ||= Eldritch::NilGroup.new
9
+ end
10
+
11
+ def in_group?
12
+ !group.nil?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,24 @@
1
1
  require 'eldritch/version'
2
+ require 'eldritch/refinements/thread'
2
3
  require 'eldritch/task'
3
4
  require 'eldritch/dsl'
4
- require 'eldritch/ext_core/thread'
5
- require 'eldritch/together'
5
+ require 'eldritch/group'
6
+ require 'eldritch/interrupted_error'
6
7
 
7
8
  module Eldritch
9
+ # Injects the DSL in the main
10
+ #
11
+ # This is automatically called when you call
12
+ # require 'eldritch'
13
+ #
14
+ # If you do not want to contaminate the main you can require +eldritch/safe+ and
15
+ # include or extend Eldricth::DSL yourself.
16
+ #
17
+ # require 'eldritch/safe'
18
+ # module Sandbox
19
+ # include Eldritch::DSL # for async blocks, together and sync
20
+ # extend Eldritch::DSL # for async method declaration
21
+ # end
8
22
  def self.inject_dsl
9
23
  Object.include Eldritch::DSL
10
24
  end
@@ -1,23 +1,65 @@
1
1
  module Eldritch
2
+ # Runs a block in parallel and allows for interaction with said block
2
3
  class Task
4
+ using Eldritch::Refinements
5
+
3
6
  attr_writer :value
7
+
8
+ # @return [Thread] underlying ruby thread
4
9
  attr_reader :thread
5
10
 
11
+ # Creates a new Task instance
12
+ #
13
+ # _Note_: this method does not yield the block directly this is done by {#start}
14
+ #
15
+ # @yield [Task] the task itself
6
16
  def initialize(&block)
7
17
  @block = block
8
18
  end
9
19
 
20
+ # Starts the task
21
+ #
22
+ # This will yield the task to the block passed in the constructor.
23
+ #
24
+ # task = Eldritch::Task.new do |task|
25
+ # # do something
26
+ # end
27
+ # task.start # calls the block in parallel
10
28
  def start
11
29
  @thread = Thread.new self, &@block
30
+ @thread.task = self
12
31
  end
13
32
 
33
+ # Waits for the task to complete
14
34
  def wait
15
35
  @thread.join
16
36
  end
17
37
 
38
+ # The return value of the task
39
+ #
40
+ # If the task is still running, it will block until it is done and then fetch the return value.
41
+ #
42
+ # @return whatever the block returns
18
43
  def value
19
44
  wait
20
45
  @value
21
46
  end
47
+
48
+ # Forces the task to end
49
+ #
50
+ # This kills the underlying thread. This is a dangerous call.
51
+ def abort
52
+ @thread.kill
53
+ end
54
+
55
+ # Interrupts the task
56
+ #
57
+ # This is done by raising an {InterruptedError} in the task block.
58
+ # This can be caught to perform cleanup before exiting.
59
+ # Tasks started with {DSL#async} will automatically handle the exception and stop cleanly.
60
+ # You can still handle the exception yourself.
61
+ def interrupt
62
+ @thread.raise InterruptedError.new
63
+ end
22
64
  end
23
65
  end
@@ -1,3 +1,3 @@
1
1
  module Eldritch
2
- VERSION = '0.1.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'eldritch/dsl'
3
3
  require 'eldritch/task'
4
- require 'eldritch/together'
4
+ require 'eldritch/group'
5
5
 
6
6
  describe Eldritch::DSL do
7
7
  let(:klass) do Class.new do
@@ -19,73 +19,90 @@ describe Eldritch::DSL do
19
19
  end
20
20
 
21
21
  describe '#together' do
22
- it 'should create a new together' do
23
- expect(Eldritch::Together).to receive(:new).and_return(double('together').as_null_object)
22
+ it 'should create a new group' do
23
+ expect(Eldritch::Group).to receive(:new).and_return(double('group').as_null_object)
24
24
 
25
25
  klass.together {}
26
26
  end
27
27
 
28
- it 'should set the current thread together' do
29
- together = double('together').as_null_object
30
- allow(Eldritch::Together).to receive(:new).and_return(together)
31
- allow(Thread.current).to receive(:together=).with(nil)
28
+ it 'should set the current thread group' do
29
+ group = double('group').as_null_object
30
+ allow(Eldritch::Group).to receive(:new).and_return(group)
31
+ allow(Thread.current).to receive(:group=).with(anything)
32
32
 
33
- expect(Thread.current).to receive(:together=).with(together)
33
+ expect(Thread.current).to receive(:group=).with(group)
34
34
 
35
35
  klass.together {}
36
36
  end
37
37
 
38
38
  it 'should wait on all tasks' do
39
- together = double('together').as_null_object
40
- allow(Eldritch::Together).to receive(:new).and_return(together)
39
+ group = double('group').as_null_object
40
+ allow(Eldritch::Group).to receive(:new).and_return(group)
41
41
 
42
- expect(together).to receive(:wait_all)
42
+ expect(group).to receive(:wait_all)
43
43
 
44
44
  klass.together {}
45
45
  end
46
46
 
47
- it 'should leave current thread together nil' do
48
- together = double('together').as_null_object
49
- allow(Eldritch::Together).to receive(:new).and_return(together)
47
+ it 'should reset the previous group when it is done' do
48
+ group = double('group').as_null_object
49
+ old_group = double('old group').as_null_object
50
+ allow(Eldritch::Group).to receive(:new).and_return(group)
51
+ allow(Thread.current).to receive(:group).and_return(old_group)
50
52
 
51
53
  klass.together {}
52
54
 
53
- expect(Thread.current.together).to be_nil
55
+ expect(Thread.current.group).to eql(old_group)
56
+ end
57
+
58
+ it 'should yield it the new group' do
59
+ group = double('group').as_null_object
60
+ allow(Eldritch::Group).to receive(:new).and_return(group)
61
+
62
+ expect{ |b| klass.together &b }.to yield_with_args(group)
54
63
  end
55
64
  end
56
65
 
57
66
  describe '#async' do
58
- context 'with 0 arguments' do
67
+ let(:task) { double('task').as_null_object }
68
+ let(:group) { double('group').as_null_object }
69
+
70
+ before do
71
+ call_me = nil
72
+ allow(Eldritch::Task).to receive(:new) do |&block|
73
+ call_me = block
74
+ task
75
+ end
59
76
 
60
- it 'should add itself to the together block if together?' do
61
- together = double('together')
62
- allow(Thread.current).to receive(:together?).and_return(true)
63
- allow(Thread.current).to receive(:together).and_return(together)
77
+ allow(group).to receive(:<<) do
78
+ call_me.call(task)
79
+ end
64
80
 
65
- expect(together).to receive(:<<).with(kind_of(Eldritch::Task))
81
+ allow(Thread.current).to receive(:group).and_return(group)
82
+ end
83
+
84
+ context 'with 0 arguments' do
85
+ it 'should add itself to the group' do
86
+ expect(group).to receive(:<<).with(task)
66
87
 
67
88
  klass.async {}
68
89
  end
69
90
 
70
91
  it 'should return a task' do
71
- expect(klass.async {}).to be_a(Eldritch::Task)
72
- end
73
-
74
- it 'should start a task' do
75
- task = double(:task)
76
- allow(Eldritch::Task).to receive(:new).and_return(task)
77
- expect(task).to receive(:start)
78
-
79
- klass.async {}
92
+ expect(klass.async {}).to eql(task)
80
93
  end
81
94
 
82
95
  it 'should set the task value' do
83
- task = double(:task)
84
- allow(Thread).to receive(:new).and_yield(task)
85
96
  expect(task).to receive(:value=).with('something')
86
97
 
87
98
  klass.async { 'something' }
88
99
  end
100
+
101
+ it 'should eat any interrupted errors' do
102
+ block = proc { raise Eldritch::InterruptedError }
103
+
104
+ expect{klass.async &block}.not_to raise_error
105
+ end
89
106
  end
90
107
 
91
108
  context 'with 1 argument' do
@@ -109,8 +126,6 @@ describe Eldritch::DSL do
109
126
 
110
127
  describe 'async method' do
111
128
  it 'should call the original' do
112
- allow(Thread).to receive(:new).and_yield(double(:task).as_null_object)
113
-
114
129
  instance = klass.new
115
130
  expect(instance).to receive(:__async_foo)
116
131
 
@@ -118,8 +133,6 @@ describe Eldritch::DSL do
118
133
  end
119
134
 
120
135
  it 'should pass all arguments' do
121
- allow(Thread).to receive(:new).and_yield(double(:task).as_null_object)
122
-
123
136
  klass.class_eval do
124
137
  async def foo(_,_,_); end
125
138
  end
@@ -130,9 +143,7 @@ describe Eldritch::DSL do
130
143
  end
131
144
 
132
145
  it 'should set the task value' do
133
- task = double(:task)
134
146
  expect(task).to receive(:value=).with(42)
135
- allow(Thread).to receive(:new).and_yield(task)
136
147
 
137
148
  klass.class_eval do
138
149
  async def foo; 42; end
@@ -141,24 +152,6 @@ describe Eldritch::DSL do
141
152
 
142
153
  instance.foo
143
154
  end
144
-
145
- it 'should return a task' do
146
- allow(Eldritch::DSL).to receive(:new).and_return(double(:task).as_null_object)
147
-
148
- instance = klass.new
149
-
150
- expect(instance.foo).to be_a(Eldritch::Task)
151
- end
152
-
153
- it 'should start the task' do
154
- task = double(:task)
155
- expect(task).to receive(:start).once
156
- allow(Eldritch::Task).to receive(:new).and_return(task)
157
-
158
- instance = klass.new
159
-
160
- instance.foo
161
- end
162
155
  end
163
156
  end
164
157
  end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'eldritch/group'
3
+
4
+ describe Eldritch::Group do
5
+ let(:group) { Eldritch::Group.new }
6
+
7
+ describe '#<<' do
8
+ it 'should start the task' do
9
+ task = double('task')
10
+ expect(task).to receive(:start)
11
+
12
+ group << task
13
+ end
14
+
15
+ it 'should add the task to the list' do
16
+ task = double('task').as_null_object
17
+
18
+ group << task
19
+
20
+ expect(group.others).to include(task)
21
+ end
22
+
23
+ context 'after it is aborted or interrupted' do
24
+ before do
25
+ group.abort
26
+ end
27
+
28
+ it 'should not start the task' do
29
+ task = double('task')
30
+
31
+ expect(task).not_to receive(:start)
32
+
33
+ group << task
34
+ end
35
+
36
+ it 'should not add the task to the list' do
37
+ task = double('task')
38
+
39
+ group << task
40
+
41
+ expect(group.others).not_to include(task)
42
+ end
43
+ end
44
+ end
45
+
46
+ describe '#others' do
47
+ it 'should return an empty array when there is only one task' do
48
+ task = double('task').as_null_object
49
+ allow(Thread.current).to receive(:task).and_return(task)
50
+
51
+ group << task
52
+
53
+ expect(group.others).to be_kind_of(Array)
54
+ expect(group.others).to be_empty
55
+ end
56
+
57
+ it 'should return all the task except the current one' do
58
+ task = double('task').as_null_object
59
+ allow(Thread.current).to receive(:task).and_return(task)
60
+ other_task = double('other task').as_null_object
61
+
62
+ group << task
63
+ group << other_task
64
+
65
+ expect(group.others).to eql([other_task])
66
+ end
67
+ end
68
+
69
+ describe '#wait_all' do
70
+ it 'should call wait on all tasks' do
71
+ task = double('task')
72
+ allow(task).to receive(:start)
73
+ group << task
74
+
75
+ expect(task).to receive(:wait)
76
+
77
+ group.wait_all
78
+ end
79
+ end
80
+
81
+ describe '#synchronize' do
82
+ it 'should yield' do
83
+ expect{|b| group.synchronize &b}.to yield_control
84
+ end
85
+ end
86
+
87
+ describe '#abort' do
88
+ it 'should call abort on all tasks' do
89
+ task = double('task').as_null_object
90
+ expect(task).to receive(:abort)
91
+
92
+ group << task
93
+ group.abort
94
+ end
95
+
96
+ it 'should not call abort on current task' do
97
+ task = double('task').as_null_object
98
+ expect(task).not_to receive(:abort)
99
+ allow(Thread.current).to receive(:task).and_return(task)
100
+
101
+ group << task
102
+ group.abort
103
+ end
104
+ end
105
+
106
+ describe '#interrupt' do
107
+ it 'should call interrupt on all tasks' do
108
+ task = double('task').as_null_object
109
+ expect(task).to receive(:interrupt)
110
+
111
+ group << task
112
+ group.interrupt
113
+ end
114
+
115
+ it 'should not call interrupt on current task' do
116
+ task = double('task').as_null_object
117
+ expect(task).not_to receive(:interrupt)
118
+ allow(Thread.current).to receive(:task).and_return(task)
119
+
120
+ group << task
121
+ group.interrupt
122
+ end
123
+ end
124
+ end
125
+
126
+ describe Eldritch::NilGroup do
127
+ let(:group) { Eldritch::NilGroup.new }
128
+
129
+ describe '#<<' do
130
+ it 'should call the task' do
131
+ task = double('task')
132
+ expect(task).to receive(:start)
133
+
134
+ group << task
135
+ end
136
+ end
137
+
138
+ describe '#nil?' do
139
+ it 'should be true' do
140
+ expect(group.nil?).to be_true
141
+ end
142
+ end
143
+ end
@@ -3,6 +3,10 @@ require 'eldritch/task'
3
3
 
4
4
  describe Eldritch::Task do
5
5
  let(:task) { Eldritch::Task.new {} }
6
+ let(:thread) { double(:thread).as_null_object }
7
+ before do
8
+ allow(Thread).to receive(:new).and_yield.and_return(thread)
9
+ end
6
10
 
7
11
  it 'should not start a thread on init' do
8
12
  expect(task.thread).to be_nil
@@ -16,7 +20,6 @@ describe Eldritch::Task do
16
20
  end
17
21
 
18
22
  it 'should call the block' do
19
- allow(Thread).to receive(:new).and_yield
20
23
  expect do |b|
21
24
  task = Eldritch::Task.new &b
22
25
  task.start
@@ -28,6 +31,12 @@ describe Eldritch::Task do
28
31
 
29
32
  task.start
30
33
  end
34
+
35
+ it 'should set the thread task' do
36
+ expect(thread).to receive(:task=).with(task)
37
+
38
+ task.start
39
+ end
31
40
  end
32
41
 
33
42
  describe '#wait' do
@@ -53,4 +62,23 @@ describe Eldritch::Task do
53
62
  expect(task.value).to eql(42)
54
63
  end
55
64
  end
65
+
66
+ describe '#abort' do
67
+ it 'should kill the thread' do
68
+ expect(thread).to receive(:kill)
69
+
70
+ task.start
71
+ task.abort
72
+ end
73
+ end
74
+
75
+ describe '#interrupt' do
76
+ it 'should raise an interrupted error on the thread' do
77
+ expect(thread).to receive(:raise).with(kind_of(Eldritch::InterruptedError))
78
+
79
+ task.start
80
+
81
+ task.interrupt
82
+ end
83
+ end
56
84
  end
@@ -1,24 +1,49 @@
1
1
  require 'spec_helper'
2
- require 'eldritch/ext_core/thread'
2
+ require 'eldritch/refinements/thread'
3
+
4
+ using Eldritch::Refinements
3
5
 
4
6
  describe Thread do
5
- it 'should have together accessor' do
6
- t = Thread.new {}
7
- expect(t).to respond_to(:together)
8
- expect(t).to respond_to(:together=)
7
+ let(:thread) { Thread.new {} }
8
+
9
+ it 'should have group accessor' do
10
+ # refinements don't work with #respond_to? and send, we have to check for errors
11
+ expect{thread.group}.not_to raise_error
12
+ expect{thread.group = nil}.not_to raise_error
13
+ end
14
+
15
+ it 'should have a task accessor' do
16
+ # refinements don't work with #respond_to? and send, we have to check for errors
17
+ expect{thread.task}.not_to raise_error
18
+ expect{thread.task = nil}.not_to raise_error
9
19
  end
10
20
 
11
- describe '#together?' do
12
- it 'should be false when together is nil' do
13
- t = Thread.new {}
14
- t.together = nil
15
- expect(t.together?).to be_false
21
+ describe '#group' do
22
+ it 'should return the togther previously set' do
23
+ group = double('group')
24
+ thread.group = group
25
+ expect(thread.group).to eql(group)
26
+ end
27
+
28
+ it 'should return a NilGroup when none are set' do
29
+ expect(thread.group).to be_a Eldritch::NilGroup
30
+ end
31
+ end
32
+
33
+ describe '#in_group?' do
34
+ it 'should be false when group is nil' do
35
+ thread.group = nil
36
+ expect(thread.in_group?).to be_false
37
+ end
38
+
39
+ it 'should be false when group is a NilGroup' do
40
+ thread.group = Eldritch::NilGroup.new
41
+ expect(thread.in_group?).to be_false
16
42
  end
17
43
 
18
- it 'should be true when together is set' do
19
- t = Thread.new {}
20
- t.together = 2
21
- expect(t.together?).to be_true
44
+ it 'should be true when group is set' do
45
+ thread.group = 2
46
+ expect(thread.in_group?).to be_true
22
47
  end
23
48
  end
24
49
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eldritch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boris Bera
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-04-11 00:00:00.000000000 Z
12
+ date: 2014-04-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -53,6 +53,20 @@ dependencies:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '2.14'
56
+ - !ruby/object:Gem::Dependency
57
+ name: yard
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 0.8.7.4
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.7.4
56
70
  description: Adds support for async methods and async blocks. Adds a together block
57
71
  that allows async methods/blocks to be controlled as a group.
58
72
  email:
@@ -72,21 +86,24 @@ files:
72
86
  - eldritch.gemspec
73
87
  - examples/async_block.rb
74
88
  - examples/async_method_with_class.rb
89
+ - examples/matrix_multiplication.rb
90
+ - examples/password_cracker.rb
75
91
  - examples/simple_async_method.rb
76
92
  - examples/together_simple.rb
77
93
  - lib/eldritch.rb
78
94
  - lib/eldritch/dsl.rb
79
- - lib/eldritch/ext_core/thread.rb
95
+ - lib/eldritch/group.rb
96
+ - lib/eldritch/interrupted_error.rb
97
+ - lib/eldritch/refinements/thread.rb
80
98
  - lib/eldritch/safe.rb
81
99
  - lib/eldritch/task.rb
82
- - lib/eldritch/together.rb
83
100
  - lib/eldritch/version.rb
84
101
  - spec/dsl_spec.rb
85
102
  - spec/eldritch_spec.rb
103
+ - spec/group_spec.rb
86
104
  - spec/spec_helper.rb
87
105
  - spec/task_spec.rb
88
106
  - spec/thread_spec.rb
89
- - spec/together_spec.rb
90
107
  homepage: https://github.com/beraboris/eldritch
91
108
  licenses:
92
109
  - MIT
@@ -99,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
99
116
  requirements:
100
117
  - - ">="
101
118
  - !ruby/object:Gem::Version
102
- version: '0'
119
+ version: '2.1'
103
120
  required_rubygems_version: !ruby/object:Gem::Requirement
104
121
  requirements:
105
122
  - - ">="
@@ -110,11 +127,12 @@ rubyforge_project:
110
127
  rubygems_version: 2.2.2
111
128
  signing_key:
112
129
  specification_version: 4
113
- summary: Adds tools to make parallelism easier.
130
+ summary: DSL that adds concurrent programming concepts to make your life easier.
114
131
  test_files:
115
132
  - spec/dsl_spec.rb
116
133
  - spec/eldritch_spec.rb
134
+ - spec/group_spec.rb
117
135
  - spec/spec_helper.rb
118
136
  - spec/task_spec.rb
119
137
  - spec/thread_spec.rb
120
- - spec/together_spec.rb
138
+ has_rdoc:
@@ -1,7 +0,0 @@
1
- class Thread
2
- attr_accessor :together
3
-
4
- def together?
5
- !together.nil?
6
- end
7
- end
@@ -1,15 +0,0 @@
1
- module Eldritch
2
- class Together
3
- def initialize
4
- @tasks = []
5
- end
6
-
7
- def <<(task)
8
- @tasks << task
9
- end
10
-
11
- def wait_all
12
- @tasks.each {|t| t.wait}
13
- end
14
- end
15
- end
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
- require 'eldritch/together'
3
-
4
- describe Eldritch::Together do
5
- describe '#wait_all' do
6
- it 'should call wait on all tasks' do
7
- together = Eldritch::Together.new
8
- task = double('task')
9
- together << task
10
-
11
- expect(task).to receive(:wait)
12
-
13
- together.wait_all
14
- end
15
- end
16
- end