Class: Sockd::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/sockd/runner.rb

Defined Under Namespace

Classes: ServiceError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name = nil, options = {}) {|_self| ... } ⇒ Runner

Returns a new instance of Runner.

Yields:

  • (_self)

Yield Parameters:

  • _self (Sockd::Runner)

    the object that the method was called on



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/sockd/runner.rb', line 16

def initialize(name = nil, options = {}, &block)
  @name = name || File.basename($0)
  @options = {
    :host      => "127.0.0.1",
    :port      => 0,
    :socket    => false,
    :mode      => 0660,
    :daemonize => true,
    :pid_path  => "/var/run/#{safe_name}.pid",
    :log_path  => false,
    :force     => false,
    :user      => false,
    :group     => false
  }.merge(options)

  [:setup, :teardown, :handle].each do |opt|
    self.public_send(opt, &options[opt]) if options[opt].respond_to?(:call)
  end

  yield self if block_given?
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



10
11
12
# File 'lib/sockd/runner.rb', line 10

def name
  @name
end

#optionsObject

Returns the value of attribute options.



10
11
12
# File 'lib/sockd/runner.rb', line 10

def options
  @options
end

Instance Method Details

#handle(message = nil, socket = nil, &block) ⇒ Object

define our socket handler by providing a block, or trigger the callback with the provided message

Raises:

  • (ArgumentError)


65
66
67
68
69
# File 'lib/sockd/runner.rb', line 65

def handle(message = nil, socket = nil, &block)
  return self if block_given? && @handle = block
  raise ArgumentError, "no message handler provided" unless @handle
  @handle.call(message, socket)
end

#log(message) ⇒ Object

output a timestamped log message



161
162
163
# File 'lib/sockd/runner.rb', line 161

def log(message)
  puts Time.now.strftime('[%d-%b-%Y %H:%M:%S] ') + message
end

#restartObject

restart our service



142
143
144
145
# File 'lib/sockd/runner.rb', line 142

def restart
  stop
  start
end

#safe_nameObject

generate a path-safe and username-safe string from our daemon name



44
45
46
# File 'lib/sockd/runner.rb', line 44

def safe_name
  name.gsub(/(^[0-9]*|[^0-9a-z])/i, '')
end

#send(message, timeout = 30) ⇒ Object

send a message to a running service and return the response



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/sockd/runner.rb', line 148

def send(message, timeout = 30)
  client do |sock|
    sock.write "#{message}\r\n"
    ready = IO.select([sock], nil, nil, timeout)
    raise ServiceError, "timed out waiting for server response" unless ready
    sock.recv(256)
  end
rescue Errno::ECONNREFUSED, Errno::ENOENT
  raise ServiceError, "#{name} process not running" unless daemon_running?
  raise ServiceError, "unable to establish connection"
end

#setup(&block) ⇒ Object

define a “setup” callback by providing a block, or trigger the callback



50
51
52
53
# File 'lib/sockd/runner.rb', line 50

def setup(&block)
  return self if block_given? && @setup = block
  @setup.call(self) if @setup
end

#startObject

start our service



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/sockd/runner.rb', line 72

def start
  server do |server|

    if options[:daemonize]
      pid = daemon_running?
      raise ServiceError, "#{name} process already running (#{pid})" if pid
      puts "starting #{name} process..."
      unless daemonize
        unless send('ping', 10).chomp == 'pong'
          raise ServiceError, "invalid ping response"
        end
        return self
      end
    end

    drop_privileges options[:user], options[:group]

    setup

    on_interrupt do |signal|
      log "#{signal} received, shutting down..."
      teardown
      # cleanup
      exit 130
    end

    log "listening on #{server.local_address.inspect_sockaddr}"

    while true
      sock = server.accept
      begin
        # wait for input
        if IO.select([sock], nil, nil, 2.0)
          msg = sock.recv(256, Socket::MSG_PEEK)
          if msg.chomp == "ping"
            sock.print "pong\r\n"
          else
            handle msg, sock
          end
        else
          log "connection timed out"
        end
      rescue Errno::EPIPE, Errno::ECONNRESET
        log "connection broken"
      end
      sock.close unless sock.closed?
    end
  end
end

#stopObject

stop our service



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sockd/runner.rb', line 123

def stop
  if daemon_running?
    pid = stored_pid
    Process.kill('TERM', pid)
    puts "SIGTERM sent to #{name} (#{pid})"
    if !wait_until(2) { daemon_stopped? pid } && options[:force]
      Process.kill('KILL', pid)
      puts "SIGKILL sent to #{name} (#{pid})"
    end
    raise ServiceError, "unable to stop #{name} process" if daemon_running?
  else
    warn "#{name} process not running"
  end
  self
rescue Errno::EPERM => e
  raise ServiceError, "unable to stop #{name} process (#{e.message})"
end

#teardown(&block) ⇒ Object

define a “teardown” callback by providing a block, or trigger the callback



57
58
59
60
# File 'lib/sockd/runner.rb', line 57

def teardown(&block)
  return self if block_given? && @teardown = block
  @teardown.call(self) if @teardown
end