1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 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
|
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = ProcessIntercom.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
# by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
require 'taskjuggler/AppConfig'
require 'taskjuggler/Log'
require 'taskjuggler/MessageHandler'
class TaskJuggler
module ProcessIntercomIface
include MessageHandler
# This function catches all unhandled exceptions in the passed block.
def trap
begin
MessageHandlerInstance.instance.trapSetup = true
res = yield
MessageHandlerInstance.instance.trapSetup = false
res
rescue => e
# Any exception here is a fatal error. We try hard to terminate the DRb
# thread and then exit the program.
begin
fatal('pi_crash_trap', "#{e}\n#{e.backtrace.join("\n")}\n\n" +
"#{'*' * 79}\nYou have triggered a bug in " +
"#{AppConfig.softwareName} version #{AppConfig.version}!\n" +
"Please see the user manual on how to get this bug fixed!\n" +
"#{'*' * 79}\n")
rescue RuntimeError
@server.terminate
return false
end
end
end
def terminate(authKey)
return false unless @server.checkKey(authKey, 'terminate')
trap { @server.terminate }
end
def connect(authKey, stdout, stderr, stdin, silent)
return false unless @server.checkKey(authKey, 'connect')
trap { @server.connect(stdout, stderr, stdin, silent) }
end
def disconnect(authKey)
return false unless @server.checkKey(authKey, 'disconnect')
trap { @server.disconnect }
end
end
module ProcessIntercom
include MessageHandler
def initIntercom
# This is the authentication key that clients will need to provide to
# execute DRb methods.
@authKey = generateAuthKey
# This flag will be set to true by DRb method calls to terminate the
# process.
@terminate = false
# This mutex is locked while a client is connected.
@clientConnection = Mutex.new
# This lock protects the @timerStart
@timeLock = Monitor.new
# The time stamp of the last client interaction.
@timerStart = nil
end
def terminate
debug('', 'Terminating on external request')
@terminate = true
end
def connect(stdout, stderr, stdin, silent)
# Set the client lock.
@clientConnection.lock
debug('', 'Rerouting ProjectServer standard IO to client')
# Make sure that all output to STDOUT and STDERR is sent to the client.
# Input is read from the client STDIN. We save a copy of the old file
# handles so we can restore then later again.
@stdout = $stdout
@stderr = $stderr
@stdin = $stdin
$stdout = stdout if stdout
$stderr = stderr if stdout
$stdin = stdin if stdin
Log.silent = silent
Term::ANSIColor.coloring = !silent
debug('', 'IO is now routed to the client')
true
end
def disconnect
debug('', 'Restoring IO')
Log.silent = true
$stdout = @stdout if @stdout
$stderr = @stderr if @stderr
$stdin = @stdin if @stdin
debug('', 'Standard IO has been restored')
# Release the client lock
@clientConnection.unlock
true
end
def generateAuthKey
rand(1000000000).to_s
end
def checkKey(authKey, command)
if authKey == @authKey
debug('', "Accepted authentication key for command '#{command}'")
else
warning('auth_key_rejected',
"Rejected wrong authentication key #{authKey}" +
"for command '#{command}'")
return false
end
true
end
# This function must be called after each client interaction to restart the
# client connection timer.
def restartTimer
@timeLock.synchronize do
debug('', 'Reseting client connection timer')
@timerStart = Time.new
end
end
# Check if the client interaction timer has already expired.
def timerExpired?
res = nil
@timeLock.synchronize do
# We should see client interaction every 2 minutes.
res = (Time.new > @timerStart + 2 * 60)
end
res
end
# This method starts a new thread and waits for the @terminate variable to
# be true. If that happens, it waits for the @clientConnection lock or
# forces an exit after the timeout has been reached. It shuts down the DRb
# server.
def startTerminator
Thread.new do
loop do
if @terminate
# We wait for the client to propery disconnect. In case this does
# not happen, we'll wait for the timeout and exit anyway.
restartTimer
while @clientConnection.locked? && !timerExpired? do
sleep 1
end
if timerExpired?
warning('drb_timeout_shutdown',
'Shutting down DRb server due to timeout')
else
debug('', 'Shutting down the DRb server')
end
DRb.stop_service
break
else
sleep 1
end
end
end
end
end
end
|