rq-ruby1.8 3.4.3

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.
Files changed (67) hide show
  1. data/Gemfile +22 -0
  2. data/Gemfile.lock +22 -0
  3. data/INSTALL +166 -0
  4. data/LICENSE +10 -0
  5. data/Makefile +6 -0
  6. data/README +1183 -0
  7. data/Rakefile +37 -0
  8. data/TODO +24 -0
  9. data/TUTORIAL +230 -0
  10. data/VERSION +1 -0
  11. data/bin/rq +902 -0
  12. data/bin/rqmailer +865 -0
  13. data/example/a.rb +7 -0
  14. data/extconf.rb +198 -0
  15. data/gemspec.rb +40 -0
  16. data/install.rb +210 -0
  17. data/lib/rq.rb +155 -0
  18. data/lib/rq/arrayfields.rb +371 -0
  19. data/lib/rq/backer.rb +31 -0
  20. data/lib/rq/configfile.rb +82 -0
  21. data/lib/rq/configurator.rb +40 -0
  22. data/lib/rq/creator.rb +54 -0
  23. data/lib/rq/cron.rb +144 -0
  24. data/lib/rq/defaultconfig.txt +5 -0
  25. data/lib/rq/deleter.rb +51 -0
  26. data/lib/rq/executor.rb +40 -0
  27. data/lib/rq/feeder.rb +527 -0
  28. data/lib/rq/ioviewer.rb +48 -0
  29. data/lib/rq/job.rb +51 -0
  30. data/lib/rq/jobqueue.rb +947 -0
  31. data/lib/rq/jobrunner.rb +110 -0
  32. data/lib/rq/jobrunnerdaemon.rb +193 -0
  33. data/lib/rq/lister.rb +47 -0
  34. data/lib/rq/locker.rb +43 -0
  35. data/lib/rq/lockfile.rb +564 -0
  36. data/lib/rq/logging.rb +124 -0
  37. data/lib/rq/mainhelper.rb +189 -0
  38. data/lib/rq/orderedautohash.rb +39 -0
  39. data/lib/rq/orderedhash.rb +240 -0
  40. data/lib/rq/qdb.rb +733 -0
  41. data/lib/rq/querier.rb +98 -0
  42. data/lib/rq/rails.rb +80 -0
  43. data/lib/rq/recoverer.rb +28 -0
  44. data/lib/rq/refresher.rb +80 -0
  45. data/lib/rq/relayer.rb +283 -0
  46. data/lib/rq/resource.rb +22 -0
  47. data/lib/rq/resourcemanager.rb +40 -0
  48. data/lib/rq/resubmitter.rb +100 -0
  49. data/lib/rq/rotater.rb +98 -0
  50. data/lib/rq/sleepcycle.rb +46 -0
  51. data/lib/rq/snapshotter.rb +40 -0
  52. data/lib/rq/sqlite.rb +286 -0
  53. data/lib/rq/statuslister.rb +48 -0
  54. data/lib/rq/submitter.rb +113 -0
  55. data/lib/rq/toucher.rb +182 -0
  56. data/lib/rq/updater.rb +94 -0
  57. data/lib/rq/usage.rb +1222 -0
  58. data/lib/rq/util.rb +304 -0
  59. data/rdoc.sh +17 -0
  60. data/rq-ruby1.8.gemspec +120 -0
  61. data/test/.gitignore +1 -0
  62. data/test/test_rq.rb +145 -0
  63. data/white_box/crontab +2 -0
  64. data/white_box/joblist +8 -0
  65. data/white_box/killrq +18 -0
  66. data/white_box/rq_killer +27 -0
  67. metadata +208 -0
@@ -0,0 +1,304 @@
1
+ unless defined? $__rq_util__
2
+ module RQ
3
+ #--{{{
4
+ LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
5
+ defined? LIBDIR
6
+
7
+ require 'pathname'
8
+ require 'socket'
9
+ require 'tmpdir'
10
+
11
+ #
12
+ # of course - all the rest goes here
13
+ #
14
+ module Util
15
+ #--{{{
16
+ class << self
17
+ def export sym
18
+ #--{{{
19
+ sym = "#{ sym }".intern
20
+ module_function sym
21
+ public sym
22
+ #--}}}
23
+ end
24
+ def append_features c
25
+ #--{{{
26
+ super
27
+ c.extend Util
28
+ #--}}}
29
+ end
30
+ end
31
+ def mcp obj
32
+ #--{{{
33
+ Marshal.load(Marshal.dump(obj))
34
+ #--}}}
35
+ end
36
+ export 'mcp'
37
+ def klass
38
+ #--{{{
39
+ self.class
40
+ #--}}}
41
+ end
42
+ export 'klass'
43
+ def realpath path
44
+ #--{{{
45
+ path = File::expand_path "#{ path }"
46
+ begin
47
+ Pathname::new(path).realpath.to_s
48
+ rescue Errno::ENOENT, Errno::ENOTDIR
49
+ path
50
+ end
51
+ #--}}}
52
+ end
53
+ export 'realpath'
54
+ def hashify(*hashes)
55
+ #--{{{
56
+ hashes.inject(accum={}){|accum,hash| accum.update hash}
57
+ #--}}}
58
+ end
59
+ export 'hashify'
60
+ def getopt opt, hash, default = nil
61
+ #--{{{
62
+ key = opt
63
+ return hash[key] if hash.has_key? key
64
+
65
+ key = "#{ key }"
66
+ return hash[key] if hash.has_key? key
67
+
68
+ key = key.intern
69
+ return hash[key] if hash.has_key? key
70
+
71
+ return default
72
+ #--}}}
73
+ end
74
+ export 'getopt'
75
+ def alive? pid
76
+ #--{{{
77
+ pid = Integer("#{ pid }")
78
+ begin
79
+ Process.kill 0, pid
80
+ true
81
+ rescue Errno::ESRCH
82
+ false
83
+ end
84
+ #--}}}
85
+ end
86
+ export 'alive?'
87
+ def maim(pid, opts = {})
88
+ #--{{{
89
+ sigs = getopt('signals', opts) || %w(SIGTERM SIGQUIT SIGKILL)
90
+ suspend = getopt('suspend', opts) || 4
91
+ pid = Integer("#{ pid }")
92
+ sigs.each do |sig|
93
+ begin
94
+ Process.kill(sig, pid)
95
+ rescue Errno::ESRCH
96
+ return nil
97
+ end
98
+ sleep 0.2
99
+ unless alive?(pid)
100
+ break
101
+ else
102
+ sleep suspend
103
+ end
104
+ end
105
+ not alive?(pid)
106
+ #--}}}
107
+ end
108
+ export 'maim'
109
+ def timestamp time = Time.now
110
+ #--{{{
111
+ usec = "#{ time.usec }"
112
+ usec << ('0' * (6 - usec.size)) if usec.size < 6
113
+ time.strftime('%Y-%m-%d %H:%M:%S.') << usec
114
+ #--}}}
115
+ end
116
+ export 'timestamp'
117
+ def stamptime string, local = true
118
+ #--{{{
119
+ string = "#{ string }"
120
+ pat = %r/^\s*(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d).(\d\d\d\d\d\d)\s*$/o
121
+ match = pat.match string
122
+ raise ArgumentError, "<#{ string.inspect }>" unless match
123
+ yyyy,mm,dd,h,m,s,u = match.to_a[1..-1].map{|m| m.to_i}
124
+ if local
125
+ Time.local yyyy,mm,dd,h,m,s,u
126
+ else
127
+ Time.gm yyyy,mm,dd,h,m,s,u
128
+ end
129
+ #--}}}
130
+ end
131
+ export 'stamptime'
132
+ def escape! s, char, esc
133
+ #--{{{
134
+ re = %r/([#{0x5c.chr << esc}]*)#{char}/
135
+ s.gsub!(re) do
136
+ (($1.size % 2 == 0) ? ($1 << esc) : $1) + char
137
+ end
138
+ #--}}}
139
+ end
140
+ export 'escape!'
141
+ def escape s, char, esc
142
+ #--{{{
143
+ ss = "#{ s }"
144
+ escape! ss, char, esc
145
+ ss
146
+ #--}}}
147
+ end
148
+ export 'escape'
149
+ def fork(*args, &block)
150
+ #--{{{
151
+ begin
152
+ verbose = $VERBOSE
153
+ $VERBOSE = nil
154
+ Process::fork(*args, &block)
155
+ ensure
156
+ $VERBOSE = verbose
157
+ end
158
+ #--}}}
159
+ end
160
+ export 'fork'
161
+ def exec(*args, &block)
162
+ #--{{{
163
+ begin
164
+ verbose = $VERBOSE
165
+ $VERBOSE = nil
166
+ Kernel::exec(*args, &block)
167
+ ensure
168
+ $VERBOSE = verbose
169
+ end
170
+ #--}}}
171
+ end
172
+ export 'exec'
173
+ def system(*args, &block)
174
+ #--{{{
175
+ begin
176
+ verbose = $VERBOSE
177
+ $VERBOSE = nil
178
+ Kernel::system(*args, &block)
179
+ ensure
180
+ $VERBOSE = verbose
181
+ end
182
+ #--}}}
183
+ end
184
+ export 'system'
185
+ def hostname
186
+ #--{{{
187
+ @__hostname__ ||= Socket::gethostname
188
+ #--}}}
189
+ end
190
+ export 'hostname'
191
+ def host
192
+ #--{{{
193
+ @__host__ ||= Socket::gethostname.gsub(%r/\..*$/o,'')
194
+ #--}}}
195
+ end
196
+ export 'host'
197
+ def emsg e
198
+ #--{{{
199
+ "#{ e.message } - (#{ e.class })"
200
+ #--}}}
201
+ end
202
+ export 'emsg'
203
+ def btrace e
204
+ #--{{{
205
+ (e.backtrace or []).join("\n")
206
+ #--}}}
207
+ end
208
+ export 'btrace'
209
+ def errmsg e
210
+ #--{{{
211
+ emsg(e) << "\n" << btrace(e)
212
+ #--}}}
213
+ end
214
+ export 'errmsg'
215
+ def erreq a, b
216
+ #--{{{
217
+ a.class == b.class and
218
+ a.message == b.message and
219
+ a.backtrace == b.backtrace
220
+ #--}}}
221
+ end
222
+ export 'erreq'
223
+ def tmpnam dir = Dir.tmpdir, seed = File::basename($0)
224
+ #--{{{
225
+ pid = Process.pid
226
+ path = "%s_%s_%s_%s_%d" %
227
+ [Util::hostname, seed, pid, Util::timestamp.gsub(/\s+/o,'_'), rand(101010)]
228
+ File::join(dir, path)
229
+ #--}}}
230
+ end
231
+ export 'tmpnam'
232
+ def uncache file
233
+ #--{{{
234
+ refresh = nil
235
+ begin
236
+ is_a_file = File === file
237
+ path = (is_a_file ? file.path : file.to_s)
238
+ stat = (is_a_file ? file.stat : File::stat(file.to_s))
239
+ refresh = tmpnam(File::dirname(path))
240
+ File::link path, refresh rescue File::symlink path, refresh
241
+ File::chmod stat.mode, path
242
+ File::utime stat.atime, stat.mtime, path
243
+ ensure
244
+ begin
245
+ File::unlink refresh if refresh
246
+ rescue Errno::ENOENT
247
+ end
248
+ end
249
+ #--}}}
250
+ end
251
+ export 'uncache'
252
+ def columnize buf, width = 80, indent = 0
253
+ #--{{{
254
+ column = []
255
+ words = buf.split %r/\s+/o
256
+ row = ' ' * indent
257
+ while((word = words.shift))
258
+ if((row.size + word.size) < (width - 1))
259
+ row << word
260
+ else
261
+ column << row
262
+ row = ' ' * indent
263
+ row << word
264
+ end
265
+ row << ' ' unless row.size == (width - 1)
266
+ end
267
+ column << row unless row.strip.empty?
268
+ column.join "\n"
269
+ #--}}}
270
+ end
271
+ export 'columnize'
272
+ def defval var, default = nil
273
+ #--{{{
274
+ v = "#{ var }"
275
+ c = "DEFAULT_#{ v }".upcase
276
+ begin
277
+ klass.send(v) || klass.const_get(c)
278
+ rescue NameError
279
+ default
280
+ end
281
+ #--}}}
282
+ end
283
+ export 'defval'
284
+ def hms s
285
+ #--{{{
286
+ h, s = s.divmod 3600
287
+ m, s = s.divmod 60
288
+ [h.to_i, m.to_i, s]
289
+ #--}}}
290
+ end
291
+ export 'hms'
292
+ def which_ruby
293
+ #--{{{
294
+ c = ::Config::CONFIG
295
+ realpath( File::join(c['bindir'], c['ruby_install_name']) << c['EXEEXT'] )
296
+ #--}}}
297
+ end
298
+ export 'which_ruby'
299
+ #--}}}
300
+ end # module Util
301
+ #--}}}
302
+ end # module RQ
303
+ $__rq_util__ = __FILE__
304
+ end
data/rdoc.sh ADDED
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+
3
+ #
4
+ # workaround so bin/* gets doc'd as ruby files
5
+ #
6
+
7
+ pushd .
8
+ cd bin
9
+ for f in *; do ln -s $f "$f.rb"; done
10
+ popd
11
+
12
+ rdoc -a -d -F -S -m README -I jpg -N [A-Z]* bin/rq.rb lib/rq.rb lib/*/*
13
+
14
+ pushd .
15
+ cd bin
16
+ for f in *rb; do rm -f $f; done
17
+ popd
@@ -0,0 +1,120 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rq-ruby1.8}
8
+ s.version = "3.4.3"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Pjotr Prins"]
12
+ s.date = %q{2011-07-21}
13
+ s.description = %q{Zero configuration job scheduler for computer clusters}
14
+ s.email = %q{pjotr.public01@thebird.nl}
15
+ s.executables = ["rqmailer", "rq"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE",
18
+ "README",
19
+ "TODO"
20
+ ]
21
+ s.files = [
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "INSTALL",
25
+ "LICENSE",
26
+ "Makefile",
27
+ "README",
28
+ "Rakefile",
29
+ "TODO",
30
+ "TUTORIAL",
31
+ "VERSION",
32
+ "bin/rq",
33
+ "bin/rqmailer",
34
+ "example/a.rb",
35
+ "extconf.rb",
36
+ "gemspec.rb",
37
+ "install.rb",
38
+ "lib/rq.rb",
39
+ "lib/rq/arrayfields.rb",
40
+ "lib/rq/backer.rb",
41
+ "lib/rq/configfile.rb",
42
+ "lib/rq/configurator.rb",
43
+ "lib/rq/creator.rb",
44
+ "lib/rq/cron.rb",
45
+ "lib/rq/defaultconfig.txt",
46
+ "lib/rq/deleter.rb",
47
+ "lib/rq/executor.rb",
48
+ "lib/rq/feeder.rb",
49
+ "lib/rq/ioviewer.rb",
50
+ "lib/rq/job.rb",
51
+ "lib/rq/jobqueue.rb",
52
+ "lib/rq/jobrunner.rb",
53
+ "lib/rq/jobrunnerdaemon.rb",
54
+ "lib/rq/lister.rb",
55
+ "lib/rq/locker.rb",
56
+ "lib/rq/lockfile.rb",
57
+ "lib/rq/logging.rb",
58
+ "lib/rq/mainhelper.rb",
59
+ "lib/rq/orderedautohash.rb",
60
+ "lib/rq/orderedhash.rb",
61
+ "lib/rq/qdb.rb",
62
+ "lib/rq/querier.rb",
63
+ "lib/rq/rails.rb",
64
+ "lib/rq/recoverer.rb",
65
+ "lib/rq/refresher.rb",
66
+ "lib/rq/relayer.rb",
67
+ "lib/rq/resource.rb",
68
+ "lib/rq/resourcemanager.rb",
69
+ "lib/rq/resubmitter.rb",
70
+ "lib/rq/rotater.rb",
71
+ "lib/rq/sleepcycle.rb",
72
+ "lib/rq/snapshotter.rb",
73
+ "lib/rq/sqlite.rb",
74
+ "lib/rq/statuslister.rb",
75
+ "lib/rq/submitter.rb",
76
+ "lib/rq/toucher.rb",
77
+ "lib/rq/updater.rb",
78
+ "lib/rq/usage.rb",
79
+ "lib/rq/util.rb",
80
+ "rdoc.sh",
81
+ "rq-ruby1.8.gemspec",
82
+ "test/.gitignore",
83
+ "test/test_rq.rb",
84
+ "white_box/crontab",
85
+ "white_box/joblist",
86
+ "white_box/killrq",
87
+ "white_box/rq_killer"
88
+ ]
89
+ s.homepage = %q{http://github.com/pjotrp/rq}
90
+ s.licenses = ["BSD"]
91
+ s.require_paths = ["lib"]
92
+ s.rubygems_version = %q{1.3.7}
93
+ s.summary = %q{Ruby Queue scheduler}
94
+
95
+ if s.respond_to? :specification_version then
96
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
97
+ s.specification_version = 3
98
+
99
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
100
+ s.add_runtime_dependency(%q<posixlock>, [">= 0"])
101
+ s.add_runtime_dependency(%q<arrayfields>, [">= 0"])
102
+ s.add_runtime_dependency(%q<lockfile>, [">= 0"])
103
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.15"])
104
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
105
+ else
106
+ s.add_dependency(%q<posixlock>, [">= 0"])
107
+ s.add_dependency(%q<arrayfields>, [">= 0"])
108
+ s.add_dependency(%q<lockfile>, [">= 0"])
109
+ s.add_dependency(%q<bundler>, ["~> 1.0.15"])
110
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
111
+ end
112
+ else
113
+ s.add_dependency(%q<posixlock>, [">= 0"])
114
+ s.add_dependency(%q<arrayfields>, [">= 0"])
115
+ s.add_dependency(%q<lockfile>, [">= 0"])
116
+ s.add_dependency(%q<bundler>, ["~> 1.0.15"])
117
+ s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
118
+ end
119
+ end
120
+
@@ -0,0 +1 @@
1
+ test_queue
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Test frame work for rq - tests submitting jobs to a queue,
4
+ # and the handling of them. Does not test NFS (yet).
5
+ #
6
+ # Copyright (C) 2011 Pjotr Prins <pjotr.prins@thebird.nl>
7
+
8
+ require 'yaml'
9
+
10
+ print "rq integration test suite by Pjotr Prins 2011\n"
11
+
12
+ src = File.join('..',File.dirname(__FILE__))
13
+ $rq = File.join(src,'bin','rq');
14
+ raise "Run from test folder!" if !File.executable?($rq)
15
+ $queue = './test_queue'
16
+
17
+ def rq_exec(args)
18
+ cmd = $rq + ' ' + $queue + ' ' + args
19
+ print "====> ",cmd," <===="
20
+ system(cmd)
21
+ end
22
+
23
+ def rq_status()
24
+ cmd = $rq + ' ' + $queue + ' status'
25
+ YAML.load(`#{cmd}`)
26
+ end
27
+
28
+ def error(line, msg)
29
+ p rq_status()
30
+ rq_exec('shutdown')
31
+ $stderr.print(__FILE__," ",line,": ",msg)
32
+ exit 1
33
+ end
34
+
35
+ def test_equal(line,s1,s2)
36
+ error(line,"#{s1} does not equal #{s2}") if s1 != s2
37
+ end
38
+
39
+ def kill_rq()
40
+ pstab = `ps xau|grep rq`
41
+ pstab.grep(/#{$rq}/) do | s |
42
+ # rq_exec('shutdown')
43
+ s =~ /\S+\s+(\d+)/
44
+ pid = $1
45
+ print "+++#{pid}+++\n"
46
+ system("kill -9 #{pid}")
47
+ sleep(1)
48
+ end
49
+ print `rm -rf #{$queue}`
50
+ pstab = `ps xau|grep rq`
51
+ pstab.grep(/#{$rq}/) do | s |
52
+ error(__LINE__,"Sorry, still running:\n"+s)
53
+ end
54
+ pstab = `ps xau|grep rq`
55
+ pstab.grep(/rq_jobrunnerdaemon/) do | s |
56
+ s =~ /\S+\s+(\d+)/
57
+ pid = $1
58
+ print "+++#{pid}+++\n"
59
+ system("kill -9 #{pid}")
60
+ sleep(1)
61
+ end
62
+ pstab = `ps xau|grep rq`
63
+ pstab.grep(/rq_jobrunnerdaemon/) do | s |
64
+ error(__LINE__,"Still running "+s)
65
+ end
66
+ end
67
+
68
+
69
+ # Check dependencies
70
+ print "Running tests...\n"
71
+
72
+ # $:.unshift '/var/lib/gems/1.8/gems/sqlite-ruby-2.2.3/lib/'
73
+ # test_equal(__LINE__,SQLite::Version::STRING,"2.2.3")
74
+
75
+ gempath = '/var/lib/gems/1.8/gems/sqlite-1.3.1/lib'
76
+ error("Expect "+gempath) if !File.directory?(gempath)
77
+ $:.unshift gempath
78
+ require 'sqlite'
79
+
80
+ kill_rq()
81
+
82
+ # In the first step we set up the queue
83
+ print `rm -rf #{$queue}`
84
+ rq_exec("create")
85
+ p rq_status()
86
+
87
+ # get status
88
+ status = rq_status()
89
+ test_equal(__LINE__,status["exit_status"],{"failures"=>0, "ok"=>0, "successes"=>0})
90
+
91
+ # submit short job
92
+ rq_exec('submit "/bin/ls /etc"')
93
+
94
+ # first time is pending
95
+ test_equal(__LINE__,rq_status()['jobs']['pending'],1)
96
+
97
+ rq_exec('submit "/bin/ls /etc"')
98
+ p rq_status()
99
+ test_equal(__LINE__,rq_status()['jobs']['total'],2)
100
+
101
+ # fire up daemon
102
+ rq_exec("feed --daemon --log=rq.log --max_feed=1 --min_sleep 1 --max_sleep 1")
103
+ 5
104
+ sleep(2)
105
+ test_equal(__LINE__,rq_status()['jobs']['total'],2)
106
+
107
+ # get status
108
+ status = rq_status()
109
+ p status
110
+ test_equal(__LINE__,status["jobs"]['total'],2)
111
+
112
+ # Now add longer jobs
113
+ rq_exec('submit "sleep 15"') # does not finish
114
+ sleep(1)
115
+ status = rq_status()
116
+ test_equal(__LINE__,status['jobs']['pending']+status['jobs']['running'],1)
117
+ rq_exec('submit "sleep 15"') # will be removed
118
+ p rq_status()
119
+ test_equal(__LINE__,rq_status()['jobs']['total'],4)
120
+
121
+ # Delete last job
122
+ rq_exec('delete 4')
123
+ sleep(2)
124
+ print "\n---\n"
125
+ print rq_exec('query state=pending')
126
+ p rq_status
127
+
128
+ # Kill rq
129
+ rq_exec('shutdown')
130
+ status = rq_status()
131
+ test_equal(__LINE__,status['jobs']['pending']+status['jobs']['running'],1)
132
+ sleep(1)
133
+ p rq_status
134
+
135
+ # Done!
136
+ print <<MSG
137
+
138
+ Congratulations, all tests passed!
139
+
140
+ This means rq accepts and executes jobs. Note, the tests
141
+ are not exhaustive, but passing them is a good sign.
142
+
143
+ MSG
144
+
145
+