ipc_transit 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/Rakefile +9 -0
- data/bin/trlist +10 -0
- data/bin/trrecv +15 -0
- data/bin/trsend +10 -0
- data/lib/ipc_transit.rb +253 -0
- data/test/tc_transit_simple.rb +76 -0
- metadata +86 -0
data/Rakefile
ADDED
data/bin/trlist
ADDED
data/bin/trrecv
ADDED
data/bin/trsend
ADDED
data/lib/ipc_transit.rb
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'SysVIPC'
|
3
|
+
include SysVIPC
|
4
|
+
|
5
|
+
##
|
6
|
+
# Fast, serverless message queueing
|
7
|
+
#
|
8
|
+
# Author:: Dana M. Diederich (diederich@gmail.com)
|
9
|
+
# Copyright:: Copyright (c) 2012 Dana M. Diederich
|
10
|
+
# License:: Distributes under the same terms as Ruby
|
11
|
+
|
12
|
+
class IPCTransit
|
13
|
+
@@queues = {}
|
14
|
+
|
15
|
+
@@ipc_transit_wire_header_args = {
|
16
|
+
'e' => {
|
17
|
+
'json' => 1,
|
18
|
+
'yaml' => 1,
|
19
|
+
},
|
20
|
+
'c' => {
|
21
|
+
'zlib' => 1,
|
22
|
+
'snappy' => 1,
|
23
|
+
'none' => 1,
|
24
|
+
},
|
25
|
+
}
|
26
|
+
@@ipc_transit_std_args = {
|
27
|
+
'message' => 1,
|
28
|
+
'qname' => 1,
|
29
|
+
'nowait' => 1,
|
30
|
+
}
|
31
|
+
|
32
|
+
|
33
|
+
##
|
34
|
+
# Send message to a queue
|
35
|
+
#
|
36
|
+
# Arguments:
|
37
|
+
# message - hash reference
|
38
|
+
# qname - name of queue to send to
|
39
|
+
# nowait - do not block if the queue is full (optional)
|
40
|
+
|
41
|
+
def self.send(args)
|
42
|
+
ret = nil
|
43
|
+
flags = IPC_NOWAIT
|
44
|
+
begin
|
45
|
+
key = self.get_queue_id(args)
|
46
|
+
mq = MessageQueue.new(key, IPC_CREAT | 0666)
|
47
|
+
ret = mq.snd(1, pack_message(args), flags)
|
48
|
+
rescue Exception => msg
|
49
|
+
puts "Exception: #{msg}"
|
50
|
+
end
|
51
|
+
return ret
|
52
|
+
end
|
53
|
+
##
|
54
|
+
# Receive a message from a queue
|
55
|
+
#
|
56
|
+
# Arguments:
|
57
|
+
# qname - name of queue to send to
|
58
|
+
# nowait - do not block if the queue is full (optional)
|
59
|
+
# raw - return the full meta-data (optional)
|
60
|
+
#
|
61
|
+
# Returns:
|
62
|
+
# Normally: message
|
63
|
+
# Raw: the message and its meta data
|
64
|
+
|
65
|
+
def self.receive(args)
|
66
|
+
ret = nil
|
67
|
+
flags = 0
|
68
|
+
if args['nowait']
|
69
|
+
flags = IPC_NOWAIT
|
70
|
+
end
|
71
|
+
begin
|
72
|
+
key = self.get_queue_id(args)
|
73
|
+
mq = MessageQueue.new(key, IPC_CREAT | 0666)
|
74
|
+
args['serialized_wire_data'] = mq.receive(0, 10000, flags)
|
75
|
+
self.unpack_data(args)
|
76
|
+
rescue Exception => msg
|
77
|
+
# puts "Exception: #{msg}"
|
78
|
+
end
|
79
|
+
if args['raw']
|
80
|
+
ret = args;
|
81
|
+
else
|
82
|
+
ret = args['message']
|
83
|
+
end
|
84
|
+
return ret
|
85
|
+
end
|
86
|
+
def self.all_queue_info()
|
87
|
+
self.gather_queue_info()
|
88
|
+
return @@queues
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.all_queues()
|
92
|
+
ret = {}
|
93
|
+
self.all_queue_info().each_pair do |qname,v|
|
94
|
+
qid = v['qid']
|
95
|
+
x = MessageQueue.new(qid, IPC_CREAT | 0666)
|
96
|
+
y = x.ipc_stat
|
97
|
+
ct = y.msg_qnum
|
98
|
+
ret[qname] = {
|
99
|
+
'qid' => qid,
|
100
|
+
'count' => ct,
|
101
|
+
}
|
102
|
+
end
|
103
|
+
return ret
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def self.get_next_id
|
108
|
+
new_id = 1
|
109
|
+
@@queues.each_pair do |k,v|
|
110
|
+
if v['qid'] > new_id
|
111
|
+
new_id = v['qid']
|
112
|
+
end
|
113
|
+
end
|
114
|
+
return new_id + 1
|
115
|
+
end
|
116
|
+
def self.get_queue_id(args)
|
117
|
+
qname = args['qname']
|
118
|
+
self.mk_queue_dir()
|
119
|
+
if @@queues[qname]
|
120
|
+
return @@queues[qname]['qid']
|
121
|
+
end
|
122
|
+
self.gather_queue_info()
|
123
|
+
if @@queues[qname]
|
124
|
+
return @@queues[qname]['qid']
|
125
|
+
end
|
126
|
+
begin
|
127
|
+
self.lock_dir()
|
128
|
+
file = File.open("/tmp/transit/#{qname}", 'w')
|
129
|
+
new_qid = get_next_id
|
130
|
+
file.puts("qid=#{new_qid}")
|
131
|
+
file.puts("qname=#{qname}")
|
132
|
+
file.close
|
133
|
+
rescue Exception => msg
|
134
|
+
self.unlock_dir()
|
135
|
+
raise msg
|
136
|
+
end
|
137
|
+
self.unlock_dir()
|
138
|
+
self.gather_queue_info()
|
139
|
+
return @@queues[qname]['qid']
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.lock_dir()
|
143
|
+
File.open('/tmp/transit.lock', File::WRONLY|File::EXCL|File::CREAT, 0666)
|
144
|
+
end
|
145
|
+
def self.unlock_dir()
|
146
|
+
File.delete('/tmp/transit.lock')
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.gather_queue_info()
|
150
|
+
self.mk_queue_dir()
|
151
|
+
Dir.glob('/tmp/transit/*').each do |filename|
|
152
|
+
info = {}
|
153
|
+
file = File.new(filename, 'r')
|
154
|
+
while (line = file.gets)
|
155
|
+
line.chomp!
|
156
|
+
(key, value) = line.split('=')
|
157
|
+
info[key] = value
|
158
|
+
end
|
159
|
+
if not info['qid']
|
160
|
+
raise "required key 'qid' not found"
|
161
|
+
end
|
162
|
+
if not info['qname']
|
163
|
+
raise "required key 'qname' not found"
|
164
|
+
end
|
165
|
+
info['qid'] = Integer(info['qid'])
|
166
|
+
@@queues[info['qname']] = info
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.mk_queue_dir()
|
171
|
+
begin
|
172
|
+
Dir.mkdir('/tmp/transit', 0777)
|
173
|
+
rescue
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
def self.transit_freeze(args)
|
179
|
+
return args['message'].to_json
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.transit_thaw(args)
|
183
|
+
return JSON.parse(args['serialized_message'])
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
#returns a scalar, ready to be sent on the wire
|
189
|
+
#it takes message and wire_meta_data
|
190
|
+
def self.pack_message(args)
|
191
|
+
args['message']['.ipc_transit_meta'] = {}
|
192
|
+
args.keys.each do |k|
|
193
|
+
next if @@ipc_transit_wire_header_args[k]
|
194
|
+
next if @@ipc_transit_std_args[k]
|
195
|
+
args['message']['.ipc_transit_meta'][k] = args[k]
|
196
|
+
end
|
197
|
+
args['serialized_message'] = self.transit_freeze(args)
|
198
|
+
self.serialize_wire_meta(args)
|
199
|
+
l = args['serialized_wire_meta_data'].length
|
200
|
+
ret = "#{l}:#{args['serialized_wire_meta_data']}#{args['serialized_message']}"
|
201
|
+
return ret
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.serialize_wire_meta(args)
|
205
|
+
s = ''
|
206
|
+
args.keys.each do |k|
|
207
|
+
if @@ipc_transit_wire_header_args[k]
|
208
|
+
#make sure a valid value is passed in
|
209
|
+
if @@ipc_transit_wire_header_args[k] == 1
|
210
|
+
s = "#{s}#{k}=#{args[k]},"
|
211
|
+
elsif @@ipc_transit_wire_header_args[k][args[k]]
|
212
|
+
s = "#{s}#{k}=#{args[k]},"
|
213
|
+
else
|
214
|
+
raise "passed wire argument #{k} had value #{args[k]} not of allowed type"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
if s
|
219
|
+
s = s[0..-2] #teh google says this is the way to remove last character
|
220
|
+
end
|
221
|
+
args['serialized_wire_meta_data'] = s
|
222
|
+
end
|
223
|
+
|
224
|
+
#NB: I know this is all hideously inefficient. I'm still learning Ruby,
|
225
|
+
#and I'm focusing on getting this correct first.
|
226
|
+
#
|
227
|
+
#returns a serialized_message and wire_meta_data
|
228
|
+
#takes serialized_wire_data
|
229
|
+
def self.unpack_data(args)
|
230
|
+
stuff = args['serialized_wire_data'].split(':')
|
231
|
+
offset = Integer(stuff.shift)
|
232
|
+
header_and_message = stuff.join(':')
|
233
|
+
if offset == 0
|
234
|
+
args['serialized_header'] = ''
|
235
|
+
else
|
236
|
+
args['serialized_header'] = header_and_message[0..offset-1]
|
237
|
+
self.thaw_wire_headers(args)
|
238
|
+
end
|
239
|
+
args['serialized_message'] = header_and_message[offset..header_and_message.length]
|
240
|
+
args['message'] = self.transit_thaw(args)
|
241
|
+
return true
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.thaw_wire_headers(args)
|
245
|
+
h = args['serialized_header']
|
246
|
+
ret = {}
|
247
|
+
h.split(',').each do |val|
|
248
|
+
(k,v) = val.split('=')
|
249
|
+
ret[k] = v
|
250
|
+
end
|
251
|
+
args['wire_headers'] = ret
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'ipc_transit'
|
3
|
+
|
4
|
+
#kind of ghetto, but I don't have a better way right now
|
5
|
+
def drain_test_queue
|
6
|
+
begin
|
7
|
+
while ret = IPCTransit.receive('qname' => 'test_qname', 'nowait' => 1)
|
8
|
+
end
|
9
|
+
rescue Exception => msg
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TestIPCTransit < Test::Unit::TestCase
|
14
|
+
def test_typical
|
15
|
+
drain_test_queue()
|
16
|
+
IPCTransit.send('message' => { 'foo' => 'bar' }, 'qname' => 'test_qname')
|
17
|
+
ret = IPCTransit.receive('qname' => 'test_qname', 'nowait' => 1)
|
18
|
+
assert(ret, 'IPCTransit.receive returned true')
|
19
|
+
assert_equal(ret['foo'], 'bar')
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_wire_raw
|
23
|
+
drain_test_queue()
|
24
|
+
IPCTransit.send('message' => { 'foo' => 'bar' }, 'qname' => 'test_qname', 'e' => 'json', 'c' => 'none')
|
25
|
+
ret = IPCTransit.receive('qname' => 'test_qname', 'raw' => 1, 'nowait' => 1)
|
26
|
+
assert(ret, 'IPCTransit.receive returned true')
|
27
|
+
assert_equal(ret['message']['foo'], 'bar')
|
28
|
+
assert_equal(ret['wire_headers']['e'], 'json')
|
29
|
+
assert_equal(ret['wire_headers']['c'], 'none')
|
30
|
+
end
|
31
|
+
def test_message_meta
|
32
|
+
drain_test_queue()
|
33
|
+
IPCTransit.send( 'qname' => 'test_qname',
|
34
|
+
'message' => { 'foo' => 'bar' },
|
35
|
+
'e' => 'json',
|
36
|
+
'c' => 'none',
|
37
|
+
'something' => 'else',
|
38
|
+
'x' => { 'this' => 'that' },
|
39
|
+
'once' => ['more',2],
|
40
|
+
)
|
41
|
+
ret = IPCTransit.receive('qname' => 'test_qname', 'nowait' => 1)
|
42
|
+
assert(ret, 'IPCTransit.receive returned true')
|
43
|
+
assert_equal(ret['foo'], 'bar')
|
44
|
+
assert_equal(ret['.ipc_transit_meta']['something'], 'else')
|
45
|
+
assert_equal(ret['.ipc_transit_meta']['x']['this'], 'that')
|
46
|
+
assert_equal(ret['.ipc_transit_meta']['once'][0], 'more')
|
47
|
+
assert_equal(ret['.ipc_transit_meta']['once'][1], 2)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_get_queue_info
|
51
|
+
drain_test_queue()
|
52
|
+
all_info = IPCTransit.all_queue_info()
|
53
|
+
IPCTransit.send('message' => { 'foo' => 'bar' }, 'qname' => 'test_qname')
|
54
|
+
assert(all_info, 'We received some queue info')
|
55
|
+
|
56
|
+
assert(all_info['test_qname']['qname'] == 'test_qname', 'test queue is in all_queue_info')
|
57
|
+
qid = all_info['test_qname']['qid']
|
58
|
+
assert(qid, 'queue_id for test_qname exists')
|
59
|
+
|
60
|
+
ret = IPCTransit.receive('qname' => 'test_qname', 'nowait' => 1)
|
61
|
+
assert(ret, 'IPCTransit.receive returned true')
|
62
|
+
assert_equal(ret['foo'], 'bar')
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_all_queues
|
66
|
+
drain_test_queue()
|
67
|
+
IPCTransit.send('message' => { 'foo' => 'bar' }, 'qname' => 'test_qname')
|
68
|
+
ret = IPCTransit.all_queues()
|
69
|
+
assert(ret, 'IPCTransit.all_queues returned true')
|
70
|
+
assert(ret['test_qname']['count'] == 1, 'exactly one message in test_qname')
|
71
|
+
ret = IPCTransit.receive('qname' => 'test_qname', 'nowait' => 1)
|
72
|
+
assert(ret, 'IPCTransit.receive returned true')
|
73
|
+
assert_equal(ret['foo'], 'bar')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ipc_transit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dana M. Diederich
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: SysVIPC
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Serverless Message Queue
|
47
|
+
email: diederich@gmail.com
|
48
|
+
executables:
|
49
|
+
- trrecv
|
50
|
+
- trsend
|
51
|
+
- trlist
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- Rakefile
|
56
|
+
- lib/ipc_transit.rb
|
57
|
+
- bin/trrecv
|
58
|
+
- bin/trsend
|
59
|
+
- bin/trlist
|
60
|
+
- test/tc_transit_simple.rb
|
61
|
+
homepage: http://rubygems.org/gems/ipc_transit
|
62
|
+
licenses: []
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.8.24
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Serverless, cross-language, fast message queue library
|
85
|
+
test_files:
|
86
|
+
- test/tc_transit_simple.rb
|