threaded 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +20 -0
- data/README.md +108 -0
- data/Rakefile +14 -0
- data/lib/threaded.rb +79 -0
- data/lib/threaded/errors.rb +5 -0
- data/lib/threaded/master.rb +80 -0
- data/lib/threaded/promise.rb +80 -0
- data/lib/threaded/timeout.rb +11 -0
- data/lib/threaded/version.rb +3 -0
- data/lib/threaded/worker.rb +48 -0
- data/test/test_helper.rb +11 -0
- data/test/threaded.rb +40 -0
- data/test/threaded/config_test.rb +40 -0
- data/test/threaded/master_test.rb +96 -0
- data/test/threaded/promise_test.rb +0 -0
- data/test/threaded/worker_test.rb +39 -0
- data/threaded.gemspec +23 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: db08fff84d920a28793a4f9d73340879fdcbb03c
|
4
|
+
data.tar.gz: 884731a111fbf0c32bcbc3b5ee4e5f105e290566
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bdb4e659e9eab461a2eaf0939b6951f269f42d41d812da326cb4de43336d6633afeb8b6287a82bcf989a31bc4d98c29fea35073bb2ff8da36ba486be9c68435d
|
7
|
+
data.tar.gz: 86e6fc98fbd9245ae59642ffd1832ffdba70abdcbeeb128cf25c7a6b153ec02a296bdf143cf8fbe55cc2e376ee3e7c0f62cd3fe794b4b1d6f5009b48c0543132
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
threaded (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
metaclass (0.0.2)
|
10
|
+
mocha (1.0.0)
|
11
|
+
metaclass (~> 0.0.1)
|
12
|
+
rake (10.1.1)
|
13
|
+
|
14
|
+
PLATFORMS
|
15
|
+
ruby
|
16
|
+
|
17
|
+
DEPENDENCIES
|
18
|
+
mocha
|
19
|
+
rake
|
20
|
+
threaded!
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
## Threaded
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/schneems/threaded.png?branch=master)](https://travis-ci.org/schneems/threaded)
|
4
|
+
|
5
|
+
Simpler than actors, easier than threads. Get threaded!
|
6
|
+
|
7
|
+
## Why
|
8
|
+
|
9
|
+
Projects like [Resque](https://github.com/resque/resque), [delayed job](https://github.com/collectiveidea/delayed_job), [queue classic](https://github.com/ryandotsmith/queue_classic), and [sidekiq](https://github.com/mperham/sidekiq) are great. They use data stores like postgres and redis to store information to be processed later. If you're prototyping a system or don't have access to a data store, you might still want to push off some work to a background process. If that's the case an in-memory threaded queue might be a good fit.
|
10
|
+
|
11
|
+
## Install
|
12
|
+
|
13
|
+
In your `Gemfile`:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'threaded'
|
17
|
+
```
|
18
|
+
|
19
|
+
Then run `$ bundle install`
|
20
|
+
|
21
|
+
## Use it
|
22
|
+
|
23
|
+
Define your task to be processed:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Archive
|
27
|
+
def self.call(repo_id, branch = 'master')
|
28
|
+
repo = Repository.find(repo_id)
|
29
|
+
repo.create_archive(branch)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
It can be any object that responds to `call` but we recommend a class or module which makes switching to a durable queue later easier.
|
35
|
+
|
36
|
+
Then to enqueue a task to be run in the background use `Threaded.enqueue`:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
repo = Repo.last
|
40
|
+
Threaded.enqueue(Archive, repo.id, 'staging')
|
41
|
+
```
|
42
|
+
|
43
|
+
The first argument is a class that defines the task to be processed and the rest of the arguments are passed to the task when it is run.
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
# Configure
|
48
|
+
|
49
|
+
The default number of worker threads is 16, you can configure that when you start your queue:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
Threaded.config do |config|
|
53
|
+
config.size = 5
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
By default jobs have a timeout value of 60 seconds. Since this is an in-memory queue (goes away when your process terminates) it is in your best interests to keep jobs small and quick, and not overload the queue. You can configure a different timeout on start:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
Threaded.config do |config|
|
61
|
+
config.timeout = 90 # timeout is in seconds
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Want a different logger? Specify a different Logger:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
Threaded.config do |config|
|
69
|
+
config.logger = Logger.new(STDOUT)
|
70
|
+
end
|
71
|
+
```
|
72
|
+
|
73
|
+
As soon as you call `enqueue` a new thread will be started, if you wish to explicitly start all threads you can call `Threaded.start`. You can also inline your config if you want when you start the queue:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Threaded.start(size: 5, timeout: 90, logger: Logger.new(STDOUT))
|
77
|
+
```
|
78
|
+
|
79
|
+
For testing or guaranteed code execution use the `inline` option:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Threaded.inline = true
|
83
|
+
```
|
84
|
+
|
85
|
+
This option bypasses the queue and executes code as it comes.
|
86
|
+
|
87
|
+
## Thread Considerations
|
88
|
+
|
89
|
+
This worker operates in the same process as your app, that means if your app is CPU bound, it will not be very useful. This worker uses threads which means that to be useful your app needs to either use IO (database calls, file writes/reads, shelling out, etc.) or run on JRuby or Rubinius.
|
90
|
+
|
91
|
+
To make sure all items in your queue are processed you can add a condition `at_exit` to your program:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
at_exit do
|
95
|
+
Threaded.stop
|
96
|
+
end
|
97
|
+
```
|
98
|
+
|
99
|
+
This call takes an optional timeout value (in seconds).
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Threaded.stop(42)
|
103
|
+
```
|
104
|
+
|
105
|
+
## License
|
106
|
+
|
107
|
+
MIT
|
108
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
task :default => [:test]
|
8
|
+
|
9
|
+
test_task = Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = false
|
14
|
+
end
|
data/lib/threaded.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'timeout'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
require 'threaded/version'
|
6
|
+
require 'threaded/timeout'
|
7
|
+
|
8
|
+
module Threaded
|
9
|
+
STOP_TIMEOUT = 10 # seconds
|
10
|
+
extend self
|
11
|
+
attr_accessor :inline, :logger, :size, :timeout
|
12
|
+
alias :inline? :inline
|
13
|
+
|
14
|
+
@mutex = Mutex.new
|
15
|
+
|
16
|
+
def start(options = {})
|
17
|
+
self.master = options
|
18
|
+
self.master.start
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure(&block)
|
23
|
+
raise "Queue is already started, must configure queue before starting" if started?
|
24
|
+
@mutex.synchronize do
|
25
|
+
yield self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias :config :configure
|
29
|
+
|
30
|
+
def started?
|
31
|
+
return false unless master
|
32
|
+
master.alive?
|
33
|
+
end
|
34
|
+
|
35
|
+
def stopped?
|
36
|
+
!started?
|
37
|
+
end
|
38
|
+
|
39
|
+
def master(options = {})
|
40
|
+
@mutex.synchronize do
|
41
|
+
return @master if @master
|
42
|
+
self.logger = options[:logger] if options[:logger]
|
43
|
+
self.size = options[:size] if options[:size]
|
44
|
+
self.timeout = options[:timeout] if options[:timeout]
|
45
|
+
@master = Master.new(logger: self.logger,
|
46
|
+
size: self.size,
|
47
|
+
timeout: self.timeout)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias :master= :master
|
51
|
+
|
52
|
+
def later(&block)
|
53
|
+
Threaded::Promise.new(&block).later
|
54
|
+
end
|
55
|
+
|
56
|
+
def enqueue(job, *args)
|
57
|
+
if inline?
|
58
|
+
job.call(*args)
|
59
|
+
else
|
60
|
+
master.enqueue(job, *args)
|
61
|
+
end
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
|
65
|
+
def stop(timeout = STOP_TIMEOUT)
|
66
|
+
return true unless master
|
67
|
+
master.stop(timeout)
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Threaded.logger = Logger.new(STDOUT)
|
73
|
+
Threaded.logger.level = Logger::INFO
|
74
|
+
|
75
|
+
|
76
|
+
require 'threaded/errors'
|
77
|
+
require 'threaded/worker'
|
78
|
+
require 'threaded/master'
|
79
|
+
require 'threaded/promise'
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Threaded
|
2
|
+
class Master
|
3
|
+
include Threaded::Timeout
|
4
|
+
attr_reader :workers, :logger
|
5
|
+
|
6
|
+
DEFAULT_TIMEOUT = 60 # seconds, 1 minute
|
7
|
+
DEFAULT_SIZE = 16
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@queue = Queue.new
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@stopping = false
|
13
|
+
@max = options[:size] || DEFAULT_SIZE
|
14
|
+
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
15
|
+
@logger = options[:logger] || Threaded.logger
|
16
|
+
@workers = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def enqueue(job, *json)
|
20
|
+
@queue.enq([job, json])
|
21
|
+
|
22
|
+
new_worker if needs_workers? && @queue.size > 0
|
23
|
+
raise NoWorkersError unless alive?
|
24
|
+
return true
|
25
|
+
end
|
26
|
+
|
27
|
+
def alive?
|
28
|
+
return false if workers.empty?
|
29
|
+
workers.detect {|w| w.alive? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
return self if alive?
|
34
|
+
@max.times { new_worker }
|
35
|
+
return self
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop(timeout = 10)
|
39
|
+
poison
|
40
|
+
timeout(timeout, "waiting for workers to stop") do
|
41
|
+
while self.alive?
|
42
|
+
sleep 0.1
|
43
|
+
end
|
44
|
+
join
|
45
|
+
end
|
46
|
+
return self
|
47
|
+
end
|
48
|
+
|
49
|
+
def size
|
50
|
+
@workers.size
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def needs_workers?
|
56
|
+
size < @max
|
57
|
+
end
|
58
|
+
|
59
|
+
def new_worker
|
60
|
+
@mutex.synchronize do
|
61
|
+
return false unless needs_workers?
|
62
|
+
return false if @stopping
|
63
|
+
@workers << Worker.new(@queue, timeout: @timeout)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def join
|
68
|
+
workers.each {|w| w.join }
|
69
|
+
return self
|
70
|
+
end
|
71
|
+
|
72
|
+
def poison
|
73
|
+
@mutex.synchronize do
|
74
|
+
@stopping = true
|
75
|
+
end
|
76
|
+
workers.each {|w| w.poison }
|
77
|
+
return self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Threaded
|
2
|
+
class Promise
|
3
|
+
class NoJobError < StandardError
|
4
|
+
def initialize
|
5
|
+
super "No job present for #{self.inspect}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :has_run; alias :has_run? :has_run
|
10
|
+
attr_reader :running; alias :running? :running
|
11
|
+
attr_reader :error
|
12
|
+
|
13
|
+
def initialize(&job)
|
14
|
+
raise "Must supply a job" unless job
|
15
|
+
@mutex = Mutex.new
|
16
|
+
@has_run = false
|
17
|
+
@running = false
|
18
|
+
@result = nil
|
19
|
+
@error = nil
|
20
|
+
@job = job
|
21
|
+
end
|
22
|
+
|
23
|
+
def later
|
24
|
+
Threaded.enqueue(self)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def call
|
29
|
+
@mutex.synchronize do
|
30
|
+
return true if running? || has_run?
|
31
|
+
begin
|
32
|
+
if @job
|
33
|
+
@running = true
|
34
|
+
@result = @job.call
|
35
|
+
else
|
36
|
+
raise NoJobError
|
37
|
+
end
|
38
|
+
rescue Exception => error
|
39
|
+
@error = error
|
40
|
+
ensure
|
41
|
+
@has_run = true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def now
|
47
|
+
wait_for_it!
|
48
|
+
raise error, error.message, error.backtrace if error
|
49
|
+
@result
|
50
|
+
end
|
51
|
+
alias :join :now
|
52
|
+
alias :value :now
|
53
|
+
|
54
|
+
private
|
55
|
+
def wait_for_it!
|
56
|
+
return true if has_run?
|
57
|
+
|
58
|
+
if running?
|
59
|
+
@mutex.synchronize {} # waits for lock to be released
|
60
|
+
else
|
61
|
+
call
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# job = Threaded.later do
|
68
|
+
|
69
|
+
|
70
|
+
# end
|
71
|
+
|
72
|
+
# job.now
|
73
|
+
|
74
|
+
# job = Threaded::Promise.new
|
75
|
+
# job.enqueue do
|
76
|
+
|
77
|
+
|
78
|
+
# end
|
79
|
+
|
80
|
+
# job.now
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Threaded
|
2
|
+
class Worker
|
3
|
+
DEFAULT_TIMEOUT = 60 # seconds, 1 minute
|
4
|
+
POISON = "poison"
|
5
|
+
include Threaded::Timeout
|
6
|
+
attr_reader :queue, :logger, :thread
|
7
|
+
|
8
|
+
def initialize(queue, options = {})
|
9
|
+
@queue = queue
|
10
|
+
@timeout = options[:timeout] || DEFAULT_TIMEOUT
|
11
|
+
@logger = options[:logger] || Threaded.logger
|
12
|
+
@thread = create_thread
|
13
|
+
end
|
14
|
+
|
15
|
+
def poison
|
16
|
+
@queue.enq(POISON)
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
puts "start is deprecated, thread is started when worker created"
|
21
|
+
end
|
22
|
+
|
23
|
+
def alive?
|
24
|
+
thread.alive?
|
25
|
+
end
|
26
|
+
|
27
|
+
def join
|
28
|
+
thread.join
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def create_thread
|
33
|
+
Thread.new {
|
34
|
+
logger.debug("Threaded In Memory Queue Worker '#{object_id}' ready")
|
35
|
+
loop do
|
36
|
+
payload = queue.pop
|
37
|
+
job, json = *payload
|
38
|
+
break if payload == POISON
|
39
|
+
|
40
|
+
self.timeout(@timeout, "job: #{job.to_s}") do
|
41
|
+
job.call(*json)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
logger.debug("Threaded In Memory Queue Worker '#{object_id}' stopped")
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/test/test_helper.rb
ADDED
data/test/threaded.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ThreadedTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_started?
|
6
|
+
Threaded.start
|
7
|
+
assert Threaded.started?
|
8
|
+
Threaded.stop
|
9
|
+
sleep 1
|
10
|
+
assert Threaded.stopped?
|
11
|
+
refute Threaded.started?
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_inline
|
15
|
+
Dummy.expects(:process).with(1).once
|
16
|
+
job = Proc.new {|x| Dummy.process(x) }
|
17
|
+
|
18
|
+
Threaded.inline = true
|
19
|
+
Threaded.enqueue(job, 1)
|
20
|
+
assert Threaded.inline
|
21
|
+
assert Threaded.stopped?
|
22
|
+
ensure
|
23
|
+
Threaded.inline = false
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_enqueues
|
27
|
+
Dummy.expects(:process).with(1).once
|
28
|
+
Dummy.expects(:process).with(2).once
|
29
|
+
|
30
|
+
Threaded.start
|
31
|
+
|
32
|
+
job = Proc.new {|x| Dummy.process(x) }
|
33
|
+
|
34
|
+
Threaded.enqueue(job, 1)
|
35
|
+
Threaded.enqueue(job, 2)
|
36
|
+
ensure
|
37
|
+
Threaded.stop
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
class ConfigTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def teardown
|
7
|
+
Threaded.stop
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_config_works
|
11
|
+
fake_out = StringIO.new
|
12
|
+
logger = Logger.new(fake_out)
|
13
|
+
size = rand(1..99)
|
14
|
+
timeout = rand(1..99)
|
15
|
+
|
16
|
+
Threaded.configure do |config|
|
17
|
+
config.size = size
|
18
|
+
config.logger = logger
|
19
|
+
config.timeout = timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
Threaded.start
|
23
|
+
|
24
|
+
assert_equal size, Threaded.size
|
25
|
+
assert_equal timeout, Threaded.timeout
|
26
|
+
assert_equal logger, Threaded.logger
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_config_cannot_call_after_start
|
30
|
+
Threaded.start
|
31
|
+
assert_raise(RuntimeError) do
|
32
|
+
Threaded.configure do |config|
|
33
|
+
config.size = size
|
34
|
+
config.logger = logger
|
35
|
+
config.timeout = timeout
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MasterTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def test_non_explicit_thread_start
|
6
|
+
Dummy.expects(:process).with(1).once
|
7
|
+
Dummy.expects(:process).with(2).once
|
8
|
+
|
9
|
+
master = Threaded::Master.new(size: 1)
|
10
|
+
|
11
|
+
job = Proc.new {|x| Dummy.process(x) }
|
12
|
+
|
13
|
+
master.enqueue(job, 1)
|
14
|
+
master.enqueue(job, 2)
|
15
|
+
master.stop
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_creates_up_to_max_workers
|
19
|
+
Dummy.expects(:process).with(1).once
|
20
|
+
Dummy.expects(:process).with(2).once
|
21
|
+
|
22
|
+
master = Threaded::Master.new(size: 2)
|
23
|
+
|
24
|
+
job = Proc.new {|x| sleep 0.5; Dummy.process(x) }
|
25
|
+
|
26
|
+
master.enqueue(job, 1)
|
27
|
+
master.enqueue(job, 2)
|
28
|
+
|
29
|
+
assert_equal 2, master.size
|
30
|
+
|
31
|
+
Dummy.expects(:process).with(3).times(3)
|
32
|
+
master.enqueue(job, 3)
|
33
|
+
master.enqueue(job, 3)
|
34
|
+
master.enqueue(job, 3)
|
35
|
+
|
36
|
+
assert_equal 2, master.size
|
37
|
+
master.stop
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_thread_worker_creation
|
41
|
+
size = 1
|
42
|
+
master = Threaded::Master.new(size: size)
|
43
|
+
master.start
|
44
|
+
assert_equal size, master.workers.size
|
45
|
+
master.stop
|
46
|
+
|
47
|
+
size = 3
|
48
|
+
master = Threaded::Master.new(size: size)
|
49
|
+
master.start
|
50
|
+
assert_equal size, master.workers.size
|
51
|
+
master.stop
|
52
|
+
|
53
|
+
size = 6
|
54
|
+
master = Threaded::Master.new(size: size)
|
55
|
+
master.start
|
56
|
+
assert_equal size, master.workers.size
|
57
|
+
master.stop
|
58
|
+
|
59
|
+
size = 16
|
60
|
+
master = Threaded::Master.new(size: size)
|
61
|
+
master.start
|
62
|
+
assert_equal size, master.workers.size
|
63
|
+
master.stop
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_calls_contents_of_blocks
|
67
|
+
Dummy.expects(:process).with(1).once
|
68
|
+
Dummy.expects(:process).with(2).once
|
69
|
+
|
70
|
+
master = Threaded::Master.new(size: 1)
|
71
|
+
master.start
|
72
|
+
|
73
|
+
job = Proc.new {|x| Dummy.process(x) }
|
74
|
+
|
75
|
+
master.enqueue(job, 1)
|
76
|
+
master.enqueue(job, 2)
|
77
|
+
master.stop
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_calls_context_of_klass
|
81
|
+
Dummy.expects(:process).with(1).once
|
82
|
+
Dummy.expects(:process).with(2).once
|
83
|
+
|
84
|
+
job = Class.new do
|
85
|
+
def self.call(num)
|
86
|
+
Dummy.process(num)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
master = Threaded::Master.new(size: 1)
|
91
|
+
master.start
|
92
|
+
master.enqueue(job, 1)
|
93
|
+
master.enqueue(job, 2)
|
94
|
+
master.stop
|
95
|
+
end
|
96
|
+
end
|
File without changes
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class WorkerTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@worker = Threaded::Worker.new(Queue.new, timeout: 1)
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_calls_contents_of_blocks
|
13
|
+
Dummy.expects(:process).with(1).once
|
14
|
+
Dummy.expects(:process).with(2).once
|
15
|
+
|
16
|
+
job = Proc.new {|x| Dummy.process(x) }
|
17
|
+
|
18
|
+
@worker.queue << [job, 1]
|
19
|
+
@worker.queue << [job, 2]
|
20
|
+
@worker.poison
|
21
|
+
@worker.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_calls_context_of_klass
|
25
|
+
Dummy.expects(:process).with(1).once
|
26
|
+
Dummy.expects(:process).with(2).once
|
27
|
+
|
28
|
+
job = Class.new do
|
29
|
+
def self.call(num)
|
30
|
+
Dummy.process(num)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@worker.queue << [job, 1]
|
35
|
+
@worker.queue << [job, 2]
|
36
|
+
@worker.poison
|
37
|
+
@worker.join
|
38
|
+
end
|
39
|
+
end
|
data/threaded.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'threaded/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "threaded"
|
8
|
+
gem.version = Threaded::VERSION
|
9
|
+
gem.authors = ["Richard Schneeman"]
|
10
|
+
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
+
gem.description = %q{ Queue stuff in memory }
|
12
|
+
gem.summary = %q{ Memory, Enqueue stuff you will }
|
13
|
+
gem.homepage = "https://github.com/schneems/threaded"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency "rake", "~> 10.1"
|
22
|
+
gem.add_development_dependency "mocha", "~> 1.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: threaded
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Schneeman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '10.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mocha
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
description: " Queue stuff in memory "
|
42
|
+
email:
|
43
|
+
- richard.schneeman+rubygems@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".travis.yml"
|
50
|
+
- Gemfile
|
51
|
+
- Gemfile.lock
|
52
|
+
- README.md
|
53
|
+
- Rakefile
|
54
|
+
- lib/threaded.rb
|
55
|
+
- lib/threaded/errors.rb
|
56
|
+
- lib/threaded/master.rb
|
57
|
+
- lib/threaded/promise.rb
|
58
|
+
- lib/threaded/timeout.rb
|
59
|
+
- lib/threaded/version.rb
|
60
|
+
- lib/threaded/worker.rb
|
61
|
+
- test/test_helper.rb
|
62
|
+
- test/threaded.rb
|
63
|
+
- test/threaded/config_test.rb
|
64
|
+
- test/threaded/master_test.rb
|
65
|
+
- test/threaded/promise_test.rb
|
66
|
+
- test/threaded/worker_test.rb
|
67
|
+
- threaded.gemspec
|
68
|
+
homepage: https://github.com/schneems/threaded
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.2.0
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: Memory, Enqueue stuff you will
|
92
|
+
test_files:
|
93
|
+
- test/test_helper.rb
|
94
|
+
- test/threaded.rb
|
95
|
+
- test/threaded/config_test.rb
|
96
|
+
- test/threaded/master_test.rb
|
97
|
+
- test/threaded/promise_test.rb
|
98
|
+
- test/threaded/worker_test.rb
|
99
|
+
has_rdoc:
|