Module: Ernicorn

Defined in:
lib/ernicorn.rb,
lib/ernicorn/server.rb,
lib/ernicorn/version.rb,
lib/ernicorn/adminrpc.rb

Defined Under Namespace

Modules: AdminRPC Classes: Mod, Server, ServerError

Constant Summary collapse

Stats =
Raindrops::Struct.new(:connections_total,
:connections_completed,
:workers_idle).new
VERSION =
'1.0.2'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.countObject

Returns the value of attribute count.



8
9
10
# File 'lib/ernicorn.rb', line 8

def count
  @count
end

.current_modObject

Returns the value of attribute current_mod.



7
8
9
# File 'lib/ernicorn.rb', line 7

def current_mod
  @current_mod
end

.logObject

Returns the value of attribute log.



7
8
9
# File 'lib/ernicorn.rb', line 7

def log
  @log
end

.modsObject

Returns the value of attribute mods.



7
8
9
# File 'lib/ernicorn.rb', line 7

def mods
  @mods
end

.virgin_proclineObject

Returns the value of attribute virgin_procline.



8
9
10
# File 'lib/ernicorn.rb', line 8

def virgin_procline
  @virgin_procline
end

Class Method Details

.dispatch(mod, fun, args) ⇒ Object

Dispatch the request to the proper mod:fun.

+mod+ is the module Symbol
+fun+ is the function Symbol
+args+ is the Array of arguments

Returns the Ruby object response



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ernicorn.rb', line 80

def self.dispatch(mod, fun, args)
  self.mods[mod] || raise(ServerError.new("No such module '#{mod}'"))
  self.mods[mod].funs[fun] || raise(ServerError.new("No such function '#{mod}:#{fun}'"))

  start = Time.now
  ret = self.mods[mod].funs[fun].call(*args)

  begin
    self.mods[mod].logger and
    self.mods[mod].logger.call(Time.now-start, [fun, *args], ret)
  rescue Object => e
    self.log.fatal(e)
  end

  ret
end

.expose(name, mixin) ⇒ Object

Expose all public methods in a Ruby module:

+name+ is the ernie module Symbol
+mixin+ is the ruby module whose public methods are exposed

Returns nothing



44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ernicorn.rb', line 44

def self.expose(name, mixin)
  context = Object.new
  context.extend mixin
  mod(name, lambda {
    mixin.public_instance_methods.each do |meth|
      fun(meth.to_sym, context.method(meth))
    end
  })
  if defined? mixin.dispatched
    self.current_mod.logger = mixin.method(:dispatched)
  end
  context
end

.fun(name, block) ⇒ Object

Record a function.

+name+ is the function Symbol
+block+ is the Block to associate

Returns nothing



35
36
37
# File 'lib/ernicorn.rb', line 35

def self.fun(name, block)
  self.current_mod.fun(name, block)
end

.logfile(file) ⇒ Object

Set the logfile to given path.

+file+ is the String path to the logfile

Returns nothing



62
63
64
# File 'lib/ernicorn.rb', line 62

def self.logfile(file)
  self.log = Logger.new(file)
end

.loglevel(level) ⇒ Object

Set the log level.

+level+ is the Logger level (Logger::WARN, etc)

Returns nothing



70
71
72
# File 'lib/ernicorn.rb', line 70

def self.loglevel(level)
  self.log.level = level
end

.mod(name, block) ⇒ Object

Record a module.

+name+ is the module Symbol
+block+ is the Block containing function definitions

Returns nothing



23
24
25
26
27
28
# File 'lib/ernicorn.rb', line 23

def self.mod(name, block)
  m = Mod.new(name)
  self.current_mod = m
  self.mods[name] = m
  block.call
end

.process(input, output) ⇒ Object

Processes a single BertRPC command.

input  - The IO to #read command BERP from.
output - The IO to #write reply BERP to.

Returns a [iruby, oruby] tuple of incoming and outgoing BERPs processed.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/ernicorn.rb', line 155

def self.process(input, output)
  self.procline('waiting')
  iruby = self.read_berp(input)
  return nil if iruby.nil?

  self.count += 1

  if iruby.size == 4 && iruby[0] == :call
    mod, fun, args = iruby[1..3]
    self.procline("#{mod}:#{fun}(#{args})")
    self.log.info("-> " + iruby.inspect) if self.log.level <= Logger::INFO
    begin
      res = self.dispatch(mod, fun, args)
      oruby = t[:reply, res]
      self.log.debug("<- " + oruby.inspect) if self.log.level <= Logger::DEBUG
      write_berp(output, oruby)
    rescue ServerError => e
      oruby = t[:error, t[:server, 0, e.class.to_s, e.message, e.backtrace]]
      self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
      self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
      write_berp(output, oruby)
    rescue Object => e
      oruby = t[:error, t[:user, 0, e.class.to_s, e.message, e.backtrace]]
      self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
      self.log.error(e.backtrace.join("\n")) if self.log.level <= Logger::ERROR
      write_berp(output, oruby)
    end
  elsif iruby.size == 4 && iruby[0] == :cast
    mod, fun, args = iruby[1..3]
    self.procline("#{mod}:#{fun}(#{args})")
    self.log.info("-> " + [:cast, mod, fun, args].inspect) if self.log.level <= Logger::INFO
    oruby = t[:noreply]
    write_berp(output, oruby)

    begin
      self.dispatch(mod, fun, args)
    rescue Object => e
      # ignore
    end
  else
    self.procline("invalid request")
    self.log.error("-> " + iruby.inspect) if self.log.level <= Logger::ERROR
    oruby = t[:error, t[:server, 0, "Invalid request: #{iruby.inspect}"]]
    self.log.error("<- " + oruby.inspect) if self.log.level <= Logger::ERROR
    write_berp(output, oruby)
  end

  self.procline('waiting')
  [iruby, oruby]
end

.procline(msg) ⇒ Object



206
207
208
# File 'lib/ernicorn.rb', line 206

def self.procline(msg)
  $0 = "ernicorn handler #{VERSION} (ruby) - #{self.virgin_procline} - [#{self.count}] #{msg}"[0..159]
end

.read_4(input) ⇒ Object

Read the length header from the wire.

+input+ is the IO from which to read

Returns the size Integer if one was read Returns nil otherwise



102
103
104
105
106
# File 'lib/ernicorn.rb', line 102

def self.read_4(input)
  raw = input.read(4)
  return nil unless raw
  raw.unpack('N').first
end

.read_berp(input) ⇒ Object

Read a BERP from the wire and decode it to a Ruby object.

+input+ is the IO from which to read

Returns a Ruby object if one could be read Returns nil otherwise



113
114
115
116
117
118
# File 'lib/ernicorn.rb', line 113

def self.read_berp(input)
  packet_size = self.read_4(input)
  return nil unless packet_size
  bert = input.read(packet_size)
  BERT.decode(bert)
end

.startObject

Start the processing loop.

Loops forever



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/ernicorn.rb', line 134

def self.start
  self.procline('starting')
  self.log.info("(#{Process.pid}) Starting") if self.log.level <= Logger::INFO
  self.log.debug(self.mods.inspect) if self.log.level <= Logger::DEBUG

  input = IO.new(3)
  output = IO.new(4)
  input.sync = true
  output.sync = true

  loop do
    process(input, output)
  end
end

.versionObject



210
211
212
# File 'lib/ernicorn.rb', line 210

def self.version
  VERSION
end

.write_berp(output, ruby) ⇒ Object

Write the given Ruby object to the wire as a BERP.

+output+ is the IO on which to write
+ruby+ is the Ruby object to encode

Returns nothing



125
126
127
128
129
# File 'lib/ernicorn.rb', line 125

def self.write_berp(output, ruby)
  data = BERT.encode(ruby)
  output.write([data.length].pack("N"))
  output.write(data)
end