gilmour 0.3.0 → 0.3.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.
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