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 +4 -4
- data/.travis.yml +1 -0
- data/README.md +27 -15
- data/Rakefile +2 -0
- data/eldritch.gemspec +5 -1
- data/examples/matrix_multiplication.rb +37 -0
- data/examples/password_cracker.rb +39 -0
- data/lib/eldritch/dsl.rb +79 -8
- data/lib/eldritch/group.rb +77 -0
- data/lib/eldritch/interrupted_error.rb +4 -0
- data/lib/eldritch/refinements/thread.rb +16 -0
- data/lib/eldritch/safe.rb +16 -2
- data/lib/eldritch/task.rb +42 -0
- data/lib/eldritch/version.rb +1 -1
- data/spec/dsl_spec.rb +49 -56
- data/spec/group_spec.rb +143 -0
- data/spec/task_spec.rb +29 -1
- data/spec/thread_spec.rb +39 -14
- metadata +26 -8
- data/lib/eldritch/ext_core/thread.rb +0 -7
- data/lib/eldritch/together.rb +0 -15
- data/spec/together_spec.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cbab97dd7492c7c19829d608f555e6cd1660901
|
4
|
+
data.tar.gz: a05f7a551bc3c73736f86846b2fced0f51000e67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 679b8be6668f63dbd2b688e94b64f30f58ec858d0d6179ddc06ccc3e434ddd4973214bf7af41846849d970888494d2c8788d07ae11e859f46949416ebedb1351
|
7
|
+
data.tar.gz: 4478f50cf4c09996245b2407007287a03368731818bdaeb466e7cd6ee6602ff321e6fc7083023f622deee32fe521e6d3a16d766809455f31fda2eef8afd7f452
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -3,9 +3,14 @@ Eldritch
|
|
3
3
|
|
4
4
|
_The dark arts of concurrent programming._
|
5
5
|
|
6
|
-
|
6
|
+
A DSL that adds parallel programming constructs to make your life a little easier.
|
7
7
|
|
8
|
-
|
8
|
+
Code quality
|
9
|
+
------------
|
10
|
+
|
11
|
+
[](http://travis-ci.org/beraboris/eldritch)
|
12
|
+
[](http://coveralls.io/r/beraboris/eldritch)
|
13
|
+
[](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.
|
66
|
-
|
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.
|
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
|
-
[](http://travis-ci.org/beraboris/eldritch)
|
100
|
-
|
101
|
-
[](http://coveralls.io/r/beraboris/eldritch)
|
102
|
-
|
103
|
-
[](http://codeclimate.com/github/beraboris/eldritch)
|
data/Rakefile
CHANGED
data/eldritch.gemspec
CHANGED
@@ -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{
|
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
|
data/lib/eldritch/dsl.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
27
|
-
|
28
|
-
|
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
|
data/lib/eldritch/safe.rb
CHANGED
@@ -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/
|
5
|
-
require 'eldritch/
|
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
|
data/lib/eldritch/task.rb
CHANGED
@@ -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
|
data/lib/eldritch/version.rb
CHANGED
data/spec/dsl_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'eldritch/dsl'
|
3
3
|
require 'eldritch/task'
|
4
|
-
require 'eldritch/
|
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
|
23
|
-
expect(Eldritch::
|
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
|
29
|
-
|
30
|
-
allow(Eldritch::
|
31
|
-
allow(Thread.current).to receive(:
|
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(:
|
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
|
-
|
40
|
-
allow(Eldritch::
|
39
|
+
group = double('group').as_null_object
|
40
|
+
allow(Eldritch::Group).to receive(:new).and_return(group)
|
41
41
|
|
42
|
-
expect(
|
42
|
+
expect(group).to receive(:wait_all)
|
43
43
|
|
44
44
|
klass.together {}
|
45
45
|
end
|
46
46
|
|
47
|
-
it 'should
|
48
|
-
|
49
|
-
|
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.
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
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
|
data/spec/group_spec.rb
ADDED
@@ -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
|
data/spec/task_spec.rb
CHANGED
@@ -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
|
data/spec/thread_spec.rb
CHANGED
@@ -1,24 +1,49 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'eldritch/
|
2
|
+
require 'eldritch/refinements/thread'
|
3
|
+
|
4
|
+
using Eldritch::Refinements
|
3
5
|
|
4
6
|
describe Thread do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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 '#
|
12
|
-
it 'should
|
13
|
-
|
14
|
-
|
15
|
-
expect(
|
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
|
19
|
-
|
20
|
-
|
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:
|
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-
|
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/
|
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: '
|
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:
|
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
|
-
|
138
|
+
has_rdoc:
|
data/lib/eldritch/together.rb
DELETED
data/spec/together_spec.rb
DELETED
@@ -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
|