message 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +121 -0
- data/lib/message/filters.rb +66 -0
- data/lib/message/in_memory_queue.rb +39 -0
- data/lib/message/job.rb +48 -0
- data/lib/message/q.rb +26 -0
- data/lib/message/worker.rb +79 -0
- data/lib/message.rb +44 -0
- metadata +65 -0
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
|
data/lib/message/job.rb
ADDED
@@ -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: []
|