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