async_io 0.0.1

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 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: