message 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # Message
2
+
3
+ Message provides flexible & reliable background/asynchronous job processing mechanism on top of simple queue interface.
4
+
5
+ Any developer can create queue adapter for Message to hook up different messaging/queue system.
6
+
7
+ One in-memory queue is included with Message for you to start development and test,
8
+ and you can easily swap in other queues later.
9
+
10
+
11
+ ## Installation
12
+
13
+
14
+
15
+ ## How to use
16
+
17
+ ### Queuing jobs
18
+
19
+
20
+ Inspired by delayed_job API, call .enq.method(params) on any object and it will be processed in the background.
21
+
22
+ # without message
23
+ @img.resize(36)
24
+
25
+ # with message
26
+ @img.enq.resize(36)
27
+
28
+ ### Start worker to process jobs
29
+
30
+ Message.worker.start
31
+
32
+ ## Job interface specification
33
+
34
+ Job = a queue + message processor
35
+
36
+ ### initialize a job
37
+
38
+ job = Message.job('name') { |msg| ... process msg ... }
39
+
40
+ ### enq(msg), alias: <<
41
+
42
+ Queue up a message for processing:
43
+
44
+ job << msg
45
+
46
+ ### process(size=1)
47
+
48
+ Process a message in queue by processor defined when initializing the job
49
+
50
+ job.process
51
+
52
+ Process multiple messages
53
+
54
+ job.process(5)
55
+
56
+ ## Job filters
57
+
58
+ You can add job filter to add additional functions to enqueue and process job message
59
+
60
+ Message.job.filter(filter_name) do |next_filter, job|
61
+ lambda do |*args, &block|
62
+ next_filter.call(*args, &block)
63
+ end
64
+ end
65
+
66
+ Filters will be applied as the order initialized.
67
+ Checkout all filters:
68
+
69
+ Message.job.filters
70
+
71
+ ### enq filter
72
+
73
+ Message.job.filter(:enq) do |filter, job|
74
+ lambda do |work|
75
+ filter.call(work)
76
+ end
77
+ end
78
+
79
+ ### process filter
80
+
81
+ Message.job.filter(:process) do |filter, job|
82
+ lambda do |size, &processor|
83
+ filter.call(size) do |msg|
84
+ processor.call(msg)
85
+ end
86
+ end
87
+ end
88
+
89
+ ## Queue adapters
90
+
91
+ Change queue adapter to change different queue implementation. Default is a in memory queue for testing and development environments.
92
+
93
+ ### Change adapter
94
+
95
+ Message.queue.adapter = :sqs
96
+
97
+ ### Add a new adapter
98
+
99
+ Message.queue.adapters[:sqs] = Message::SqsQueue
100
+
101
+ ## Queue interface specification
102
+
103
+ To hook up a new queue into Message.
104
+
105
+ ### name
106
+
107
+ Queue name.
108
+
109
+ ### enq(msg), alias: <<
110
+
111
+ Enqueue message, non-blocking.
112
+
113
+ ### deq(size, &block)
114
+
115
+ Dequeue message, non-blocking.
116
+ It is up to Queue implementation when to delete the message in queue when deq got called with a block.
117
+
118
+ ### size
119
+
120
+ (Approximate) queue size.
121
+
@@ -0,0 +1,66 @@
1
+ require 'benchmark'
2
+
3
+ module Message
4
+ class Filters
5
+ attr_reader :config
6
+
7
+ def initialize
8
+ @data = Hash.new{|h,k|h[k]=[]}
9
+ @config = {
10
+ :error_handling_callback => lambda {|type, job, msg, e| }
11
+ }
12
+ defaults.each do |t, m|
13
+ self[t] << [m, method(m)]
14
+ end
15
+ end
16
+
17
+ def [](type)
18
+ @data[type]
19
+ end
20
+
21
+ def defaults
22
+ [
23
+ [:process, :error_handling],
24
+ [:enq, :error_handling],
25
+ [:process, :benchmarking]
26
+ ]
27
+ end
28
+
29
+ def error_handling(filter, job)
30
+ lambda do |arg, &processor|
31
+ type = processor ? :process : :enq
32
+ log_error(type, job, (type == :enq ? arg : nil)) do
33
+ if processor
34
+ filter.call(arg) do |msg|
35
+ log_error(type, job, msg) { processor.call(msg) }
36
+ end
37
+ else
38
+ filter.call(arg)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def benchmarking(filter, job)
45
+ lambda do |size, &processor|
46
+ filter.call(size) do |msg|
47
+ ret = nil
48
+ Message.logger.info { "#{job.name}: processing one message"}
49
+ s = Benchmark.realtime do
50
+ ret = processor.call(msg)
51
+ end
52
+ Message.logger.info { "#{job.name}: processed in #{(1000 * s).to_i}ms" }
53
+ ret
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+ def log_error(type, job, msg, &block)
60
+ block.call
61
+ rescue => e
62
+ Message.logger.error {"#{type} #{job.name} message failed, #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"}
63
+ config[:error_handling_callback].call(type, job, msg, e)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,39 @@
1
+ require 'thread'
2
+
3
+ module Message
4
+ class InMemoryQueue
5
+ attr :name
6
+ def initialize(name)
7
+ @name = name
8
+ @queue = ::Queue.new
9
+ end
10
+
11
+ def enq(msg)
12
+ @queue << msg
13
+ end
14
+ alias :<< :enq
15
+
16
+ def deq(size=1, &block)
17
+ if size == 1
18
+ __deq__(&block)
19
+ else
20
+ size.times { __deq__(&block) }
21
+ end
22
+ rescue ThreadError
23
+ #no message in queue
24
+ end
25
+
26
+ def size
27
+ @queue.size
28
+ end
29
+
30
+ private
31
+ def __deq__(&block)
32
+ if block_given?
33
+ yield(@queue.deq(true))
34
+ else
35
+ @queue.deq(true)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ require 'message/filters'
2
+
3
+ module Message
4
+ class Job
5
+ class << self
6
+ def filters
7
+ @filters ||= Filters.new
8
+ end
9
+
10
+ def filter(type, name, &block)
11
+ filters[type] << [name, block]
12
+ end
13
+
14
+ def reset
15
+ @filters = nil
16
+ end
17
+ end
18
+
19
+ def initialize(queue, &processor)
20
+ @queue = queue
21
+ @processor = processor || lambda {|msg| msg}
22
+ end
23
+
24
+ def name
25
+ @queue.name
26
+ end
27
+
28
+ def size
29
+ @queue.size
30
+ end
31
+
32
+ def enq(msg)
33
+ chain(:enq, @queue.method(:enq)).call(msg)
34
+ end
35
+ alias :<< :enq
36
+
37
+ def process(size=1)
38
+ chain(:process, @queue.method(:deq)).call(size, &@processor)
39
+ end
40
+
41
+ private
42
+ def chain(type, base)
43
+ Job.filters[type].reverse.inject(base) do |m, f|
44
+ f[1].call(m, self)
45
+ end
46
+ end
47
+ end
48
+ end
data/lib/message/q.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'message/in_memory_queue'
2
+ module Message
3
+ module Q
4
+ module_function
5
+ def init(name)
6
+ adapters[adapter].new(name)
7
+ end
8
+
9
+ def adapters
10
+ @adapters ||= { :in_memory => InMemoryQueue }
11
+ end
12
+
13
+ def adapter
14
+ @adapter ||= :in_memory
15
+ end
16
+
17
+ def adapter=(name)
18
+ @adapter = name
19
+ end
20
+
21
+ def reset
22
+ @adapter = nil
23
+ @adapters = nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+
3
+ module Message
4
+ class Worker
5
+ DEFAULT_JOB_NAME = 'message-worker-default'
6
+
7
+ module Enqueue
8
+ class Enq
9
+ def initialize(obj, job)
10
+ @obj = obj
11
+ @job = job
12
+ end
13
+
14
+ def method_missing(m, *args, &block)
15
+ if block_given?
16
+ raise ArgumentError, "Can't enqueue with block call."
17
+ end
18
+ unless @obj.respond_to?(m)
19
+ raise NoMethodError, "undefined method `#{m}' for #{@obj.inspect}"
20
+ end
21
+ Worker.enq(@job, [@obj, m, args])
22
+ end
23
+ end
24
+
25
+ def enq(job=DEFAULT_JOB_NAME)
26
+ Enq.new(self, job)
27
+ end
28
+ end
29
+
30
+ class << self
31
+ def jobs
32
+ @jobs ||= RUBY_PLATFORM =~ /java/ ? java.util.concurrent.ConcurrentHashMap.new : {}
33
+ end
34
+
35
+ def job(name)
36
+ jobs[name] ||= Message.job(name, &job_processor)
37
+ end
38
+
39
+ def enq(name, work)
40
+ job(name).enq(YAML.dump(work))
41
+ end
42
+
43
+ def job_processor
44
+ lambda do |msg|
45
+ obj, m, args = YAML.load(msg)
46
+ obj.send(m, *args)
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(job_name)
52
+ @job_name = job_name || DEFAULT_JOB_NAME
53
+ end
54
+
55
+ def start(size=10, interval=1)
56
+ Thread.start do
57
+ begin
58
+ log(:info) { "start" }
59
+ loop do
60
+ process(size)
61
+ sleep interval
62
+ end
63
+ log(:info) { "stopped" }
64
+ rescue => e
65
+ log(:error) { "crashed: #{e.message}\n#{e.backtrace.join("\n")}"}
66
+ end
67
+ end
68
+ end
69
+
70
+ def process(size=1)
71
+ Worker.job(@job_name).process(size)
72
+ end
73
+
74
+ private
75
+ def log(level, &block)
76
+ Message.logger.send(level) { "[Worker(#{Thread.current.object_id})] #{block.call}" }
77
+ end
78
+ end
79
+ end
data/lib/message.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'message/q'
2
+ require 'message/job'
3
+ require 'message/worker'
4
+ require 'logger'
5
+
6
+ module Message
7
+ module_function
8
+ def queue(name=nil)
9
+ if name
10
+ Q.init(name)
11
+ else
12
+ Q
13
+ end
14
+ end
15
+
16
+ def job(name=nil, &block)
17
+ if name
18
+ Job.new(queue(name), &block)
19
+ else
20
+ Job
21
+ end
22
+ end
23
+
24
+ def worker(job=nil)
25
+ Worker.new(job)
26
+ end
27
+
28
+ def logger
29
+ @logger ||= Logger.new(STDOUT)
30
+ end
31
+
32
+ def logger=(logger)
33
+ @logger = logger
34
+ end
35
+
36
+ def reset
37
+ Message.queue.reset
38
+ Message.job.reset
39
+ end
40
+
41
+ reset
42
+ end
43
+
44
+ Object.send(:include, Message::Worker::Enqueue)
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: message
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Xiao Li
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-09 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'Message provides flexible & reliable background/asynchronous job processing
15
+ mechanism on top of simple queue interface.
16
+
17
+
18
+ Any developer can create queue adapter for Message to hook up different messaging/queue
19
+ system.
20
+
21
+
22
+ One in-memory queue is included with Message for you to start development and test,
23
+
24
+ and you can easily swap in other queues later.
25
+
26
+ '
27
+ email:
28
+ - swing1979@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/message/filters.rb
35
+ - lib/message/in_memory_queue.rb
36
+ - lib/message/job.rb
37
+ - lib/message/q.rb
38
+ - lib/message/worker.rb
39
+ - lib/message.rb
40
+ homepage: https://github.com/xli/message
41
+ licenses:
42
+ - MIT
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.23
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Simplify Ruby messaging/queue/async/job processing.
65
+ test_files: []