message 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.
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: []