lib/tork/server.rb in tork-19.4.0 vs lib/tork/server.rb in tork-19.5.0
- old
+ new
@@ -1,66 +1,71 @@
require 'socket'
require 'json'
require 'shellwords'
require 'set'
+require 'tork/bridge'
module Tork
class Server
def self.address program=$0
".#{program}.sock"
end
def initialize
+ begin
+ @welcome = UNIXServer.open(address = Server.address)
+ # UNIX domain socket files are not automatically deleted on close
+ at_exit { File.delete address if File.socket? address }
+ rescue Errno::EADDRINUSE
+ # another instance of this program is already running in the same
+ # directory so become a remote control for it rather than exiting
+ warn "#{$0}: remotely controlling existing instance..."
+ exec 'tork-remote', $0
+ end
+
# only JSON messages are supposed to be emitted on STDOUT
# so make puts() in the user code write to STDERR instead
@stdout = STDOUT.dup
STDOUT.reopen STDERR
- @clients = Set.new.add(STDIN)
- @servers = Set.new
- @address = Server.address
+ @servers = Set.new.add(@welcome)
+ @clients = Set.new; join STDIN # parent process connected on STDIN
end
def loop
- begin
- server = UNIXServer.open(@address)
- rescue SystemCallError => error
- warn "#{$0}: #{error}; retrying in #{timeout = 1 + rand(10)} seconds..."
- sleep timeout
- retry
- end
-
catch :quit do
- @servers.add server
while @clients.include? STDIN
IO.select((@servers + @clients).to_a).first.each do |stream|
- @client = stream
+ if stream == @welcome
+ join stream.accept
- if stream == server
- @clients.add stream.accept
-
elsif (stream.eof? rescue true)
- @clients.delete stream
+ part stream
- elsif @command = hear(stream, stream.gets)
+ elsif @command = hear(stream, stream.gets) and not @command.empty?
recv stream, @command
end
end
end
end
- ensure
- # UNIX domain socket files are not deleted automatically upon closing
- File.delete @address if File.socket? @address
end
def quit
throw :quit
end
protected
+ def join client
+ @clients.add client
+ end
+
+ def part client
+ @clients.delete client
+ end
+
# Returns nil if the message received was not meant for processing.
def hear sender, message
JSON.load message
rescue JSON::ParserError => error
if @clients.include? sender
@@ -71,11 +76,13 @@
tell @clients, message, false
nil
end
end
+ # Sets the @client variable to the client we are currently serving.
def recv client, command
+ @client = client
__send__(*command)
rescue => error
tell client, error
end
@@ -91,11 +98,11 @@
if prefix
message = Array(message).join("\n").gsub(/^/, "#{$0}: ")
end
targets =
- if one_or_more_clients.kind_of? IO
+ if one_or_more_clients.respond_to? :to_io
[one_or_more_clients]
else
Array(one_or_more_clients)
end
@@ -111,29 +118,16 @@
end
end
end
def popen command
- child = IO.popen(command, 'r+')
+ child = Bridge.new(command)
@servers.add child
child
end
def pclose child
- return unless @servers.delete? child
-
- # this should be enough to stop programs that use Tork::Server#loop
- # because their IO.select() loop terminates on the closing of STDIN
- child.close_write
-
- # but some programs like tork-herald(1) need to be killed explicitly
- # because they do not follow our convention of exiting on STDIN close
- Process.kill :SIGTERM, child.pid
- Process.waitpid child.pid
-
- # this will block until the child process has exited so we must kill it
- # explicitly (as above) to ensure that this program does not hang here
- child.close_read
+ child.disconnect if @servers.delete? child
end
end
end