Class: TaskJuggler::ProjectServer
- Includes:
- ProcessIntercom
- Defined in:
- lib/taskjuggler/daemon/ProjectServer.rb
Overview
The ProjectServer objects are created from the ProjectBroker to handle the data of a particular project. Each ProjectServer runs in a separate process that is forked-off in the constructor. Any action such as adding more files or generating a report will cause the process to fork again, creating a ReportServer object. This way the initially loaded project can be modified but the original version is always preserved for subsequent calls. Each ProjectServer process has a unique secret authentication key that only the ProjectBroker knows. It will pass it with the URI of the ProjectServer to the client to permit direct access to the ProjectServer.
Instance Attribute Summary collapse
-
#authKey ⇒ Object
readonly
Returns the value of attribute authKey.
-
#uri ⇒ Object
readonly
Returns the value of attribute uri.
Instance Method Summary collapse
-
#getProjectName ⇒ Object
Return the name of the loaded project or nil.
-
#getReportList ⇒ Object
Return a list of the HTML reports defined for the project.
-
#getReportServer ⇒ Object
This function triggers the creation of a new ReportServer process.
-
#initialize(daemonAuthKey, projectData = nil, logConsole = false) ⇒ ProjectServer
constructor
A new instance of ProjectServer.
-
#loadProject(args) ⇒ Object
Wait until the project load has been finished.
-
#ping ⇒ Object
This function is called regularly by the ProjectBroker process to check that the ProjectServer is still operating properly.
Methods included from ProcessIntercom
#checkKey, #connect, #disconnect, #generateAuthKey, #initIntercom, #restartTimer, #startTerminator, #terminate, #timerExpired?
Methods included from MessageHandler
#critical, #debug, #error, #fatal, #info, #warning
Constructor Details
#initialize(daemonAuthKey, projectData = nil, logConsole = false) ⇒ ProjectServer
Returns a new instance of ProjectServer.
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 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 40 def initialize(daemonAuthKey, projectData = nil, logConsole = false) @daemonAuthKey = daemonAuthKey @projectData = projectData # Since we are still in the ProjectBroker process, the current DRb # server is still the ProjectBroker DRb server. @daemonURI = DRb.current_server.uri # Used later to store the DRbObject of the ProjectBroker. @daemon = nil initIntercom @logConsole = logConsole @pid = nil @uri = nil # A reference to the TaskJuggler object that holds the project data. @tj = nil # The current state of the project. @state = :new # A time stamp when the last @state update happened. @stateUpdated = TjTime.new # A lock to protect access to @state @stateLock = Monitor.new # A Queue to asynchronously generate new ReportServer objects. @reportServerRequests = Queue.new # A list of active ReportServer objects @reportServers = [] @reportServers.extend(MonitorMixin) @lastPing = TjTime.new # We've started a DRb server before. This will continue to live somewhat # in the child. All attempts to create a DRb connection from the child # to the parent will end up in the child again. So we use a Pipe to # communicate the URI of the child DRb server to the parent. The # communication from the parent to the child is not affected by the # zombie DRb server in the child process. rd, wr = IO.pipe if (@pid = fork) == -1 fatal('ps_fork_failed', 'ProjectServer fork failed') elsif @pid.nil? # This is the child if @logConsole # If the Broker wasn't daemonized, log stdout and stderr to PID # specific files. $stderr.reopen("tj3d.ps.#{$$}.stderr", 'w') $stdout.reopen("tj3d.ps.#{$$}.stdout", 'w') end begin $SAFE = 1 DRb.install_acl(ACL.new(%w[ deny all allow 127.0.0.1 ])) iFace = ProjectServerIface.new(self) begin @uri = DRb.start_service('druby://127.0.0.1:0', iFace).uri debug('', "Project server is listening on #{@uri}") rescue error('ps_cannot_start_drb', "ProjectServer can't start DRb: #{$!}") end # Send the URI of the newly started DRb server to the parent process. rd.close wr.write @uri wr.close # Start a Thread that waits for the @terminate flag to be set and does # other background tasks. startTerminator # Start another Thread that will be used to fork-off ReportServer # processes. startHousekeeping # Cleanup the DRb threads DRb.thread.join debug('', 'Project server terminated') exit 0 rescue => exception # TjRuntimeError exceptions are simply passed through. if exception.is_a?(TjRuntimeError) raise TjRuntimeError, $! end error('ps_cannot_start_drb', "ProjectServer can't start DRb: #{$!}") end else # This is the parent Process.detach(@pid) wr.close @uri = rd.read rd.close end end |
Instance Attribute Details
#authKey ⇒ Object (readonly)
Returns the value of attribute authKey.
38 39 40 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 38 def authKey @authKey end |
#uri ⇒ Object (readonly)
Returns the value of attribute uri.
38 39 40 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 38 def uri @uri end |
Instance Method Details
#getProjectName ⇒ Object
Return the name of the loaded project or nil.
184 185 186 187 188 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 184 def getProjectName return nil unless @tj restartTimer @tj.projectName end |
#getReportList ⇒ Object
Return a list of the HTML reports defined for the project.
191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 191 def getReportList return [] unless @tj && (project = @tj.project) list = [] project.reports.each do |report| unless report.get('formats').empty? list << [ report.fullId, report.name ] end end restartTimer list end |
#getReportServer ⇒ Object
This function triggers the creation of a new ReportServer process. It will return the URI and the authentication key of this new server.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 205 def getReportServer # ReportServer objects only make sense for successfully scheduled # projects. return [ nil, nil ] unless @state == :ready # The ReportServer will be created asynchronously in another Thread. To # find it in the @reportServers list, we create a unique tag to identify # it. tag = rand(99999999999999) debug('', "Pushing #{tag} onto report server request queue") @reportServerRequests.push(tag) # Now wait until the new ReportServer shows up in the list. reportServer = nil while reportServer.nil? @reportServers.synchronize do @reportServers.each do |rs| reportServer = rs if rs.tag == tag end end # It should not take that long, so we use a short idle time here. sleep 0.1 if reportServer.nil? end debug('', "Got report server with URI #{reportServer.uri} for " + "tag #{tag}") restartTimer [ reportServer.uri, reportServer.authKey ] end |
#loadProject(args) ⇒ Object
Wait until the project load has been finished. The result is true if the project scheduled without errors. Otherwise the result is false. args is an Array of Strings. The first element is the working directory. The second one is the master project file (.tjp file). Additionally a list of optional .tji files can be provided.
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 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 139 def loadProject(args) dirAndFiles = args.dup.untaint # The first argument is the working directory Dir.chdir(args.shift.untaint) # Save a time stamp of when the project file loading started. @modifiedCheck = TjTime.new updateState(:loading, dirAndFiles, false) begin @tj = TaskJuggler.new # Make sure that trace reports get CSV formats included so there # reports can be generated on request. @tj.generateTraces = true # Parse all project files unless @tj.parse(args, true) warning('parse_failed', "Parsing of #{args.join(' ')} failed") updateState(:failed, nil, false) @terminate = true return false end # Then schedule the project unless @tj.schedule warning('schedule_failed', "Scheduling of project #{@tj.projectId} failed") updateState(:failed, @tj.projectId, false) @terminate = true return false end rescue TjRuntimeError updateState(:failed, nil, false) @terminate = true return false end # Great, everything went fine. We've got a project to work with. updateState(:ready, @tj.projectId, false) debug('', "Project #{@tj.projectId} loaded") restartTimer true end |
#ping ⇒ Object
This function is called regularly by the ProjectBroker process to check that the ProjectServer is still operating properly.
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/taskjuggler/daemon/ProjectServer.rb', line 237 def ping # Store the time stamp. If we don't get the ping for some time, we # assume the ProjectBroker has died. @lastPing = TjTime.new # Now also check our ReportServers if they are still there. If not, we # can remove them from the @reportServers list. @reportServers.synchronize do deadServers = [] @reportServers.each do |rs| unless rs.ping deadServers << rs end end @reportServers.delete_if { |rs| deadServers.include?(rs) } end end |