eldritch 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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