async_io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6987084439444f8b65360b694efe1367a3abda31
4
+ data.tar.gz: 875b9b38b82ed030e711d84790029f1d0034bbc5
5
+ SHA512:
6
+ metadata.gz: 994b5606932c7a7e2b9c5d3052f1468a9a08a85448ab009801a00da31b7d8c09328747f95af1ef5d4d4624345bf0434a8caa27d9c3336ace26825e4b2d30c8d1
7
+ data.tar.gz: 24629df537bf4b3f7313a2fbb5340a3577d4733b24394cb42ac854407f532d13708f6c14a1dc12843ca744d8c0a60d03b178d2d89e8d10ccfda2fca1adcbcdba
@@ -0,0 +1,97 @@
1
+ require 'thread'
2
+ require 'async_io/worker'
3
+ require 'async_io/rescuer'
4
+
5
+ module AsyncIO
6
+ class Base
7
+ include AsyncIO::Rescuer
8
+
9
+ ##
10
+ # Default:
11
+ # Number of threads to be spanwed is 1
12
+ #
13
+ # NOTE: Any sort of exception raised while
14
+ # 'getting' a job done will not be raised at all.
15
+ # Instead it will be logged to a specified log file.
16
+ #
17
+ # Whenever an exception is raised, the thread that the
18
+ # exception was raised from is killed, so we need a
19
+ # way to prevent threads from being killed. Therefore it
20
+ # rescues all exceptions raised and logs them.
21
+ #
22
+ attr_reader :queue, :threads
23
+ attr_accessor :logger
24
+ def initialize(n_threads=1)
25
+ @logger = AsyncIO::Logger
26
+ @queue = Queue.new
27
+ @threads = []
28
+ n_threads.times { @threads << Thread.new { consumer } }
29
+ end
30
+
31
+ ##
32
+ # Ruby Queue#pop sets non_block to false.
33
+ # It waits until data is pushed on to
34
+ # the queue and then process it.
35
+ #
36
+ def consumer
37
+ rescuer do
38
+ while worker = queue.pop
39
+ worker.call
40
+ end
41
+ end
42
+ end
43
+ private(:consumer)
44
+
45
+ ##
46
+ # It creates a new Worker, pushes it onto the queue,
47
+ # whenever a 'job' (i.e a Ruby object ) is finished
48
+ # it calls the payload and passes the result of job
49
+ # to it.
50
+ #
51
+ # For example:
52
+ #
53
+ # def aget_user(uid, &payload)
54
+ # worker(payload) do
55
+ # User.find(ui)
56
+ # end
57
+ # end
58
+ #
59
+ # It returns the worker created for a particular job
60
+ # which you could send message +done+ to it in order
61
+ # to retrieve its job done.
62
+ # see prediction_io/worker.rb
63
+ #
64
+ # For example:
65
+ # result = aget_user(1) { |u| Logger.info(u.name) }
66
+ #
67
+ # # job may take a while to be done...
68
+ #
69
+ # user = result.done
70
+ # user.name
71
+ # => "John"
72
+ #
73
+ # NOTE: Whenever you use the snippet above, if the job
74
+ # has not been finished yet you will get +false+
75
+ # whenever you send a message +job+ to it. Once
76
+ # job is finished you will be able to get its result.
77
+ #
78
+ def worker(payload, &job)
79
+ rescuer do
80
+ Worker.new(payload, job).tap { |w|
81
+ queue.push(w)
82
+ }
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Perform any sort of task that needs to be
88
+ # asynchronously done.
89
+ # NOTE: It does not return anything, as it receives
90
+ # and empty job. ( i.e empty block of code )
91
+ #
92
+ def async(&payload)
93
+ worker(payload) { }
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1 @@
1
+ require 'async_io/async_io'
@@ -0,0 +1,19 @@
1
+ module AsyncIO
2
+ module Rescuer
3
+
4
+ ##
5
+ # Rescues any sort of exception raised and
6
+ # log it to a default logger, returns :rescued
7
+ # if any exception was raised.
8
+ #
9
+ def rescuer
10
+ begin
11
+ yield
12
+ rescue Exception => notice
13
+ AsyncIO::Logger.error("[-:AsyncIO::AsyncIO:-] - #{notice}\n")
14
+ :rescued
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module AsyncIo
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,80 @@
1
+ require 'ostruct'
2
+ require 'timeout'
3
+ require 'async_io/rescuer'
4
+
5
+ module AsyncIO
6
+ class Worker
7
+ include AsyncIO::Rescuer
8
+
9
+ attr_reader :payload, :job
10
+ attr_reader :finished, :done
11
+
12
+ def initialize(payload, job)
13
+ @payload = payload
14
+ @job = job
15
+ @done = false
16
+ @finished = false
17
+ end
18
+
19
+ ##
20
+ # It sends payload a message +call+
21
+ # and passes the result of a job, by sending
22
+ # job a message +call+ as well, as its argument.
23
+ # This allows us to do:
24
+ #
25
+ # aget_user(1) { |u| print u.name }
26
+ #=> Paul Clark Manson
27
+ #
28
+ # Or any other sort of task that you may
29
+ # need its result to be available within a block without
30
+ # needing to wait for it to finish and non blocking IO.
31
+ #
32
+ # A payload is a Ruby object must pass, for example:
33
+ #
34
+ # payload = lambda { |u| print u.name }
35
+ # payload = Object.new
36
+ # def payload.call(result); warn(result); end
37
+ #
38
+ # job is pre-definied inside a method, it can
39
+ # be anything, for example:
40
+ # worker(payload) do
41
+ # User.find(uid)
42
+ # end
43
+ #
44
+ def call
45
+ try do
46
+ @finished = true
47
+ @done = job.call
48
+ payload.call(done)
49
+ end
50
+ end
51
+
52
+ ##
53
+ # Tries to get the first job done, when an exception
54
+ # is raised it then calls payload again passing a
55
+ # fallback as its argument.
56
+ #
57
+ def try
58
+ begin
59
+ yield
60
+ rescue Exception => notice
61
+ rescuer { payload.call(fallback(notice)) }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ ##
68
+ # Instances of OpenStruct returns nil when the method
69
+ # called does not exist. This prevents another exception
70
+ # from being raised.
71
+ # It returns an instance of OpenStruct object with the
72
+ # exception rescued ( i.e notice ) and the worker that
73
+ # was assigned to this particular job.
74
+ #
75
+ def fallback(notice)
76
+ OpenStruct.new({ notice: notice, worker: self })
77
+ end
78
+
79
+ end
80
+ end
data/lib/async_io.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'async_io/load'
2
+
3
+ module AsyncIO
4
+
5
+ def self.async_creator=(new_async)
6
+ @@async_creator = new_async
7
+ end
8
+
9
+ def self.async_creator
10
+ @@async_creator ||= Base.new(5)
11
+ end
12
+
13
+ ##
14
+ # Creates async jobs, if a payload (i.e ruby block)
15
+ # is not given it passes an empty payload to woker.
16
+ # That allows us to do:
17
+ #
18
+ # User.aget(1)
19
+ # User.aget(1) { |u| print u.id }
20
+ #
21
+ # The response will be a worker that was created for this
22
+ # particular job.
23
+ #
24
+ # NOTE: If you read PredictionIO::Worker you will see that
25
+ # it calls payload and passes job as its arguments. This is
26
+ # how it is available within a block later on.
27
+ # NOTE: You must pass a job ( i.e ruby block ).
28
+ #
29
+ def self.async(payload, &job)
30
+ payload = payload || ->(n) { n }
31
+ async_creator.worker(payload, &job)
32
+ end
33
+
34
+ end
@@ -0,0 +1,140 @@
1
+ require 'spec_helper'
2
+ require 'async_io/base'
3
+ require 'stringio'
4
+
5
+ module AsyncIO
6
+ Base.class_eval do
7
+ ##
8
+ # Returns queue's size and pops one
9
+ # item from queue.
10
+ def extract_size!
11
+ queue.size.tap { queue.pop }
12
+ end
13
+
14
+ public(:consumer)
15
+ end
16
+
17
+ end
18
+
19
+ describe AsyncIO::Base do
20
+
21
+ before { Thread.stub(:new).and_return(double) }
22
+ let(:alien) { AsyncIO::Base.new }
23
+ let(:logger) { AsyncIO::Logger }
24
+
25
+ context "initialising" do
26
+ it "should have a queue" do
27
+ alien.queue.should respond_to :push
28
+ end
29
+
30
+ it "should create 1 thread as default" do
31
+ alien.threads.size.should eq(1)
32
+ end
33
+
34
+ end
35
+
36
+ context "#worker" do
37
+
38
+ it "should have a worker" do
39
+ alien.should respond_to :worker
40
+ end
41
+
42
+ it "should_not raise_error when no block is passed" do
43
+ expect {
44
+ alien.worker(:jason)
45
+ }.to_not raise_error
46
+ end
47
+
48
+ it "should not raise_error when block is passed" do
49
+ expect {
50
+ alien.worker(:lizza) { }
51
+ }.to_not raise_error
52
+ end
53
+
54
+ it "should push worker onto the queue" do
55
+ alien.worker(:paul) { }
56
+ alien.extract_size!.should eq(1)
57
+ end
58
+
59
+ it "should return worker" do
60
+ result = alien.worker(:blunt) { }
61
+ result.should be_instance_of AsyncIO::Worker
62
+ end
63
+ end
64
+
65
+ context "#async" do
66
+ it { alien.should respond_to :async }
67
+
68
+ ##
69
+ # NOTE: this snippet here can be a little tricky
70
+ # First we create a lambda ( -> its just Ruby syntatic sugar )
71
+ # Second we create a double ( a mock )
72
+ # Third we stub out the state of our original
73
+ # method and return the double
74
+ #
75
+ # Then we call the method +async+ which takes a block to be
76
+ # 'passed' an object.
77
+ #
78
+ # Last but not least we check if our object has received
79
+ # that message with that particular argument.
80
+ #
81
+ # Ruby allows us to do some cool stuff with coercion and
82
+ # closures while using blocks.
83
+ # The following might be helpul:
84
+ # robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas
85
+
86
+ #
87
+ it "should pass this payload block onto Worker" do
88
+ payload = -> { :im_an_async_job }
89
+
90
+ worker = double
91
+ alien.stub(:worker).
92
+ and_return(worker)
93
+
94
+ ##
95
+ # See the link below for a better understading how to
96
+ # pass/call blocks of code in Ruby.
97
+ # pragdave.pragprog.com/pragdave/2005/11/symbolto_proc.html
98
+ #
99
+ alien.async(&payload)
100
+
101
+ alien.should have_received(:worker).with(payload)
102
+ end
103
+ end
104
+
105
+ context "#consumer" do
106
+ it { alien.should respond_to :consumer }
107
+
108
+ it "should pop and consume items in a queue" do
109
+ bob = double
110
+
111
+ ##
112
+ # returns false to break the while loop.
113
+ #
114
+ bob.should_receive(:pop).and_return(false)
115
+ alien.stub(:queue).and_return(bob)
116
+
117
+ alien.consumer.should be_nil
118
+ end
119
+
120
+ it "should not raise any error" do
121
+ tob = -> { raise "Not going to be Raised" }
122
+ alien.queue.push(tob)
123
+
124
+ expect {
125
+ alien.consumer
126
+ }.to_not raise_error
127
+ end
128
+
129
+ it "should call a worker" do
130
+ duck = double
131
+ duck.should_receive(:call)
132
+
133
+ alien.stub(:queue).and_return([duck])
134
+
135
+ alien.consumer
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'async_io/rescuer.rb'
3
+
4
+ class Guard
5
+ include AsyncIO::Rescuer
6
+
7
+ def initialize
8
+ @logger = AsyncIO::Logger
9
+ end
10
+
11
+ end
12
+
13
+ describe AsyncIO::Rescuer do
14
+ let(:logger) { AsyncIO::Logger }
15
+ let(:guard) { Guard.new }
16
+
17
+ it { guard.should respond_to :rescuer }
18
+
19
+ context "#rescuer" do
20
+
21
+ it "should rescue when an exception is raised" do
22
+ expect {
23
+ guard.rescuer { raise "hell" }
24
+ }.to_not raise_error
25
+ end
26
+
27
+ it "should not raise when no block" do
28
+ expect {
29
+ guard.rescuer
30
+ }.to_not raise_error
31
+ end
32
+
33
+ it "should not raise when block is present" do
34
+ expect {
35
+ guard.rescuer { :imma_block }
36
+ }.to_not raise_error
37
+ end
38
+ end
39
+
40
+ context "Logs", "whenever an exception is raised" do
41
+
42
+ it "should write error to logger" do
43
+ guard.rescuer { raise "log me!" }
44
+ logger.read.should match /log me!/
45
+ end
46
+
47
+ end
48
+ end
49
+
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+ require 'async_io/worker'
3
+
4
+ module AsyncIO
5
+ Worker.class_eval do
6
+ public(:fallback)
7
+ end
8
+
9
+ describe Worker do
10
+
11
+ let(:payload) { ->(r) { r } }
12
+ let(:job) { -> { :am_a_dog } }
13
+ let(:worker) { Worker.new(payload, job) }
14
+
15
+ context "initialising" do
16
+
17
+ it "must take a payload" do
18
+ worker.payload.should be_kind_of Proc
19
+ end
20
+
21
+ it "must take a job" do
22
+ worker.job.should be_kind_of Proc
23
+ end
24
+
25
+ end
26
+
27
+ it { worker.should respond_to :done }
28
+
29
+ it "should not be done as job is not finished" do
30
+ worker.done.should be_false
31
+ end
32
+
33
+ it "must call payload and pass job as its argument" do
34
+ worker.call.should eq(:am_a_dog)
35
+ end
36
+
37
+ context "Getting a job done" do
38
+ it "should set done to its last finished job" do
39
+ worker.call
40
+ worker.done.should eq(:am_a_dog)
41
+ end
42
+ end
43
+
44
+ describe "#try" do
45
+ it { worker.should respond_to :try }
46
+
47
+ context "failed" do
48
+ it "should call fallback" do
49
+ worker.should_receive(:fallback)
50
+ worker.try { raise "failed" }
51
+ end
52
+ end
53
+
54
+ context "success" do
55
+ it "should not call fallback" do
56
+ worker.should_not_receive(:fallback)
57
+ worker.try { :happy }
58
+ end
59
+ end
60
+
61
+ context "#fallback" do
62
+ let(:fallback) { worker.fallback("notice") }
63
+
64
+ it "should have a notice" do
65
+ fallback.notice.should eq("notice")
66
+ end
67
+
68
+ it "should have a worker" do
69
+ fallback.worker.should eq worker
70
+ end
71
+
72
+ it "should return nil when method not found" do
73
+ fallback.will_return_nil.should be_nil
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,30 @@
1
+ module AsyncIO
2
+ CONFIG_PATH = File.expand_path("../support", __FILE__)
3
+
4
+ Logger = Object.new
5
+ def Logger.error(n); @n = n; end
6
+ def Logger.read; @n; end
7
+ end
8
+
9
+ RSpec.configure do |c|
10
+ c.treat_symbols_as_metadata_keys_with_true_values = true
11
+ end
12
+
13
+ class AsyncIO::FakeAsync
14
+ ##
15
+ # Calls payload and passes whatever
16
+ # job.call yields as its argument.
17
+ #
18
+ def self.async(payload, &job)
19
+ payload = payload || ->(n) { n }
20
+ payload.call(job.call)
21
+ end
22
+
23
+ def worker(payload, &job)
24
+ payload.call(job.call)
25
+ end
26
+
27
+ end
28
+
29
+ RSpec::Matchers.define(:be_rescued){ |e| match { |a| a === :rescued } }
30
+ RSpec::Matchers.define(:be_deleted){ |e| match { |a| a === :deleted } }
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async_io
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Antonio C Nalesso
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Perform asyncrhonous IO for ruby using blocks and threads just pure old
28
+ ruby code.
29
+ email:
30
+ - acnalesso@yahoo.co.uk
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - lib/async_io.rb
36
+ - lib/async_io/base.rb
37
+ - lib/async_io/load.rb
38
+ - lib/async_io/rescuer.rb
39
+ - lib/async_io/version.rb
40
+ - lib/async_io/worker.rb
41
+ - spec/async_io/base_spec.rb
42
+ - spec/async_io/rescuer_spec.rb
43
+ - spec/async_io/worker_spec.rb
44
+ - spec/spec_helper.rb
45
+ homepage: https://github.com/acnalesso/async_io
46
+ licenses:
47
+ - MIT
48
+ metadata: {}
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 2.0.3
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Simple asynchronous IO for ruby.
69
+ test_files:
70
+ - spec/async_io/base_spec.rb
71
+ - spec/async_io/rescuer_spec.rb
72
+ - spec/async_io/worker_spec.rb
73
+ - spec/spec_helper.rb
74
+ has_rdoc: