gilmour 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb4068ed285f59f1c81d6466d2e28520971f467c
4
- data.tar.gz: f1a1d0696b0fee3123231e0e7fa19daa50553904
3
+ metadata.gz: 762241aef84bf02c572d89ac9f22c16b710911dc
4
+ data.tar.gz: 50f90851b8bbe757576118d44480b4d102aa6d0e
5
5
  SHA512:
6
- metadata.gz: 5a4c2e55ff101684ece9fdaed73c56303e21d2e310454b8454b4c00dacef3a5ac877c1b01068948dbf82fcf40728b0b1ee6915c458ed687e7db0c19aad96c0af
7
- data.tar.gz: df8085937a9af0f214683dd20b00d484ccd5da65d501c823569c58036c59377d7bba1c123e3329aacdd3f2f41f7ac583b3ad38d63f29bcba36512d0d7f257d81
6
+ metadata.gz: d91f45f9377ff883ff8c371c21212e2702323649c4d45d819bf405bbb570aa3ccd0dff4c401a85d5d59e6a745b2a2398f456947bdc83183c51dc99a0ed31e573
7
+ data.tar.gz: 8202e63ba3940829ccac7a89aeba02336f0b2f6b18001c73d3549da496a4576ddb54d01b6ded31472bc2af8891bab5d24b8512bed8dda696065b35c63a1a2b7c
data/gilmour.gemspec CHANGED
@@ -3,24 +3,24 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
  require "./version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "gilmour"
7
- s.version = Gilmour::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Aditya Godbole"]
10
- s.email = ["code.aa@gdbl.me"]
11
- s.homepage = ""
12
- s.summary = %q{A Sinatra like DSL for implementing AMQP services}
13
- s.description = %q{This gem provides a Sinatra like DSL and a simple protocol to enable writing services that communicate over AMQP}
6
+ s.name = "gilmour"
7
+ s.version = Gilmour::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Aditya Godbole", "Piyush Verma"]
10
+ s.email = ["code.aa@gdbl.me", "piyush@piyushverma.net"]
11
+ s.homepage = ""
12
+ s.summary = %q{A Sinatra like DSL for implementing AMQP services}
13
+ s.description = %q{This gem provides a Sinatra like DSL and a simple protocol to enable writing services that communicate over AMQP}
14
14
 
15
- s.add_development_dependency "rspec"
16
- s.add_development_dependency "rspec-given"
17
- s.add_dependency "mash"
18
- s.add_dependency "redis"
19
- s.add_dependency "gilmour-em-hiredis"
20
- s.add_dependency "amqp"
15
+ s.add_development_dependency "rspec"
16
+ s.add_development_dependency "rspec-given"
17
+ s.add_dependency "mash"
18
+ s.add_dependency "redis"
19
+ s.add_dependency "gilmour-em-hiredis"
20
+ s.add_dependency "amqp"
21
21
 
22
- s.files = `git ls-files`.split("\n")
23
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
- s.require_paths = ["lib"]
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
26
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'json'
4
4
  require 'logger'
5
+ require_relative './stdhijack'
6
+ require_relative './waiter'
5
7
 
6
8
  # Top level module
7
9
  module Gilmour
@@ -20,9 +22,23 @@ module Gilmour
20
22
  end
21
23
 
22
24
  class Responder
25
+ CAPTURE_STDOUT = false
26
+ LOG_SEPERATOR = '%%'
27
+ LOG_PREFIX = "#{LOG_SEPERATOR}gilmour#{LOG_SEPERATOR}"
28
+
23
29
  attr_reader :logger
24
30
  attr_reader :request
25
31
 
32
+ def fork_logger
33
+ logger = Logger.new(STDERR)
34
+ loglevel = ENV["LOG_LEVEL"] ? ENV["LOG_LEVEL"].to_sym : :warn
35
+ logger.level = Gilmour::LoggerLevels[loglevel] || Logger::WARN
36
+ logger.formatter = proc do |severity, datetime, progname, msg|
37
+ "#{LOG_PREFIX}#{severity}#{LOG_SEPERATOR}#{msg}"
38
+ end
39
+ logger
40
+ end
41
+
26
42
  def make_logger
27
43
  logger = Logger.new(STDERR)
28
44
  original_formatter = Logger::Formatter.new
@@ -82,53 +98,129 @@ module Gilmour
82
98
  end
83
99
  end
84
100
 
101
+ def pub_relay(waiter)
102
+ Thread.new {
103
+ waiter.add 1
104
+ loop {
105
+ begin
106
+ data = @read_publish_pipe.readline
107
+ destination, message = JSON.parse(data)
108
+ @backend.publish(message, destination)
109
+ rescue EOFError
110
+ waiter.done
111
+ rescue Exception => e
112
+ GLogger.debug e.message
113
+ GLogger.debug e.backtrace
114
+ end
115
+ }
116
+ }
117
+ end
118
+
119
+ def io_readers(parent_io, waiter)
120
+ io_threads = []
121
+
122
+ parent_io.each do |reader|
123
+ io_threads << Thread.new {
124
+ waiter.add 1
125
+ loop {
126
+ begin
127
+ data = reader.readline.chomp
128
+ if data.empty?
129
+ next
130
+ end
131
+
132
+ if data.start_with?(LOG_PREFIX)
133
+ data.split(LOG_PREFIX).each do |msg|
134
+ msg_grp = msg.split(LOG_SEPERATOR, 2)
135
+
136
+ if msg_grp.length > 1
137
+ data = msg_grp[1]
138
+ case msg_grp[0]
139
+ when 'INFO'
140
+ logger.info data
141
+ when 'UNKNOWN'
142
+ logger.unknown data
143
+ when 'WARN'
144
+ logger.warn data
145
+ when 'ERROR'
146
+ logger.error data
147
+ when 'FATAL'
148
+ logger.fatal data
149
+ else
150
+ logger.debug data
151
+ end
152
+ else
153
+ logger.debug msg
154
+ end
155
+
156
+ end
157
+ next
158
+ end
159
+
160
+ logger.debug data
161
+ rescue EOFError
162
+ waiter.done
163
+ rescue Exception => e
164
+ GLogger.error e.message
165
+ GLogger.error e.backtrace
166
+ end
167
+ }
168
+ }
169
+ end
170
+
171
+ io_threads
172
+ end
173
+
85
174
  # Called by parent
86
175
  # :nodoc:
87
176
  def execute(handler)
88
177
  if @multi_process
89
178
  GLogger.debug "Executing #{@sender} in forked moode"
90
- @read_pipe = @pipe[0]
91
- @write_pipe = @pipe[1]
92
179
 
93
- @read_publish_pipe = @publish_pipe[0]
94
- @write_publish_pipe = @publish_pipe[1]
180
+ @read_pipe, @write_pipe = @pipe
181
+ @read_publish_pipe, @write_publish_pipe = @publish_pipe
182
+
183
+ out_r, out_w = IO.pipe
184
+ parent_io = [out_r]
185
+ child_io = [out_w]
186
+
187
+ if CAPTURE_STDOUT == true
188
+ err_r, err_w = IO.pipe
189
+ child_io << err_w
190
+ parent_io << err_r
191
+ end
95
192
 
96
193
  pid = Process.fork do
97
194
  @backend.stop
98
195
  EventMachine.stop_event_loop
196
+
197
+ #Close the parent channels in forked process
99
198
  @read_pipe.close
100
199
  @read_publish_pipe.close
200
+ parent_io.each{|io| io.close}
201
+
101
202
  @response_sent = false
102
- _execute(handler)
203
+ @logger = fork_logger
204
+
205
+ capture_output(child_io, CAPTURE_STDOUT) {
206
+ _execute(handler)
207
+ }
103
208
  end
104
209
 
210
+ # Cleanup the writers in Parent process.
211
+ child_io.each {|io| io.close }
212
+
105
213
  @write_pipe.close
106
214
  @write_publish_pipe.close
107
215
 
108
- pub_mutex = Mutex.new
109
-
110
- pub_reader = Thread.new {
111
- loop {
112
- begin
113
- data = @read_publish_pipe.readline
114
- pub_mutex.synchronize do
115
- destination, message = JSON.parse(data)
116
- @backend.publish(message, destination)
117
- end
118
- rescue EOFError
119
- # awkward blank rescue block
120
- rescue Exception => e
121
- GLogger.debug e.message
122
- GLogger.debug e.backtrace
123
- end
124
- }
125
- }
216
+ wg = Gilmour::Waiter.new
217
+ io_threads = io_readers(parent_io, wg)
218
+ io_threads << pub_relay(wg)
126
219
 
127
220
  begin
128
221
  receive_data(@read_pipe.readline)
129
222
  rescue EOFError => e
130
223
  logger.debug e.message
131
- logger.debug "EOFError caught in responder.rb, because of nil response"
132
224
  end
133
225
 
134
226
  pid, status = Process.waitpid2(pid)
@@ -146,12 +238,18 @@ module Gilmour
146
238
  write_response(@sender, msg, 500)
147
239
  end
148
240
 
149
- pub_mutex.synchronize do
150
- pub_reader.kill
241
+ @read_pipe.close
242
+
243
+ wg.wait do
244
+ io_threads.each { |th|
245
+ th.kill
246
+ }
151
247
  end
152
248
 
153
- @read_pipe.close
249
+ # Cleanup.
154
250
  @read_publish_pipe.close
251
+ parent_io.each{|io| io.close unless io.closed?}
252
+
155
253
  else
156
254
  _execute(handler)
157
255
  end
@@ -162,7 +260,7 @@ module Gilmour
162
260
  # supplied at setup.
163
261
  def emit_error(message, code = 500, extra = {})
164
262
  opts = {
165
- topic: @request.topic,
263
+ topic: @request.topic,
166
264
  request_data: @request.body,
167
265
  userdata: JSON.generate(extra || {}),
168
266
  sender: @sender,
@@ -0,0 +1,49 @@
1
+ # StringIO also do as IO, but IO#reopen fails.
2
+ # The problem is that a StringIO cannot exist in the O/S's file descriptor
3
+ # table. STDERR.reopen(...) at the low level does a dup() or dup2() to
4
+ # copy one file descriptor to another.
5
+ #
6
+ # I have two options:
7
+ #
8
+ # (1) $stderr = StringIO.new
9
+ # Then any program which writes to $stderr will be fine. But anything
10
+ # which writes to STDERR will still go to file descriptor 2.
11
+ #
12
+ # (2) reopen STDERR with something which exists in the O/S file descriptor
13
+ # table: e.g. a file or a pipe.
14
+ #
15
+ # I canot use a file, hence a Pipe.
16
+
17
+ def capture_output(pipes, capture_stdout=false)
18
+ streams = []
19
+
20
+ if capture_stdout == true
21
+ streams << $stdout
22
+ end
23
+
24
+ streams << $stderr
25
+
26
+ # Save the streams to be reassigned later.
27
+ # Actually it doesn't matter because the child process would be killed
28
+ # anyway after the work is done.
29
+ saved = streams.each do |stream|
30
+ stream.dup
31
+ end
32
+
33
+ begin
34
+ streams.each_with_index do |stream, ix|
35
+ # Probably I should not use IX, otherwise stdout and stderr can arrive
36
+ # out of order, which they should?
37
+ # If I reopen both of them on the same PIPE, they are guaranteed to
38
+ # arrive in order.
39
+ stream.reopen(pipes[ix])
40
+ end
41
+ yield
42
+ ensure
43
+ # This is sort of meaningless, just makes sense aesthetically.
44
+ # To return what was borrowed.
45
+ streams.each_with_index do |stream, i|
46
+ stream.reopen(saved[i])
47
+ end
48
+ end
49
+ end
@@ -1,11 +1,25 @@
1
1
  module Gilmour
2
2
  class Waiter
3
3
  def initialize
4
+ @count = 0
4
5
  @done = false
5
6
  @waiter_m = Mutex.new
6
7
  @waiter_c = ConditionVariable.new
7
8
  end
8
9
 
10
+ def add n = 1
11
+ synchronize { @count += n }
12
+ end
13
+
14
+ def done
15
+ synchronize do
16
+ @count -= n
17
+ if @count == 0
18
+ signal
19
+ end
20
+ end
21
+ end
22
+
9
23
  def synchronize(&blk)
10
24
  @waiter_m.synchronize(&blk)
11
25
  end
@@ -13,12 +27,33 @@ module Gilmour
13
27
  def signal
14
28
  synchronize do
15
29
  @done = true
30
+ @count = 0
16
31
  @waiter_c.signal
17
32
  end
18
33
  end
19
34
 
20
35
  def wait(timeout=nil)
21
36
  synchronize { @waiter_c.wait(@waiter_m, timeout) unless @done }
37
+ yield if block_given?
22
38
  end
23
39
  end
24
40
  end
41
+
42
+ def test
43
+ wg = Gilmour::Waiter.new
44
+ wg.add 3
45
+
46
+ 3.times do
47
+ Thread.new {
48
+ t = rand(10000) / 10000.0
49
+ sleep(t)
50
+ puts "done\n"
51
+ wg.done
52
+ }
53
+ end
54
+
55
+ wg.wait do
56
+ puts "All jobs done"
57
+ end
58
+
59
+ end
data/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Gilmour
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gilmour
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aditya Godbole
8
+ - Piyush Verma
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-07-10 00:00:00.000000000 Z
12
+ date: 2015-07-28 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rspec
@@ -98,6 +99,7 @@ description: This gem provides a Sinatra like DSL and a simple protocol to enabl
98
99
  writing services that communicate over AMQP
99
100
  email:
100
101
  - code.aa@gdbl.me
102
+ - piyush@piyushverma.net
101
103
  executables: []
102
104
  extensions: []
103
105
  extra_rdoc_files: []
@@ -117,6 +119,7 @@ files:
117
119
  - lib/gilmour/base.rb
118
120
  - lib/gilmour/protocol.rb
119
121
  - lib/gilmour/responder.rb
122
+ - lib/gilmour/stdhijack.rb
120
123
  - lib/gilmour/waiter.rb
121
124
  - test/spec/helpers/common.rb
122
125
  - test/spec/helpers/connection.rb