- server: backed out recent changes to "start"; they broke something

svn path=/trunk/boinc/; revision=18403
This commit is contained in:
David Anderson 2009-06-12 22:25:22 +00:00
parent 91aec4a4b8
commit 311815e60a
2 changed files with 24 additions and 122 deletions

View File

@ -5527,7 +5527,7 @@ Rom 12 June 2009
boinccas.dll
boinccas95.dll
David 11 June 2009
David 12 June 2009
- web: fix up country flags a little
html/
@ -5537,14 +5537,14 @@ David 11 June 2009
user/
main.css
Rom 11 June 2009
Rom 12 June 2009
- PTPSCR: Updates from Carl
clientscr/progress/win/x86/
boincscr.exe
gradient.jpg
Rom 11 June 2009
Rom 12 June 2009
- MGR: Introduce the notion of a Return URL to the attach to
account manager wizard. When successfully attached the
account manager can have the manager launch a browser to
@ -5558,7 +5558,7 @@ Rom 11 June 2009
CompletionPage.cpp
WizardAttachProject.cpp, .h
Rom 11 June 2009
Rom 12 June 2009
- MGR: Introduce the cookie detection failure URL to the
acct_mgr_logon.xml file. If the cookies cannot be found provide
a hyperlink control on the AcctountInfo page in the wizard
@ -5575,3 +5575,9 @@ Rom 11 June 2009
lib/
gui_rpc_client.h
gui_rpc_client_ops.cpp
David 12 June 2009
- server: backed out recent changes to "start"; they broke something
sched/
start

View File

@ -1,19 +1,14 @@
#!/usr/bin/env python
# -*- mode: python; python-indent: 4; -*-
## $Id$
'''
A program to start/stop BOINC server daemons and run periodic tasks.
Parses config.xml and runs <daemon> and <task> entries.
The format of config.xml is described in boinc/doc/configuration.php.
The main script is "start"; sym-link or hard-link "start" to "stop", "cron".
Invocation methods:
--enable (default if invoked as "start")
Set the project to ENABLED mode and start daemons
--cron If project is in ENABLED mode start daemons and run tasks;
else do nothing.
This command is intended to be run as a real cron job
@ -21,25 +16,18 @@ Invocation methods:
--cron-tasks Run tasks but do not start daemons;
This command is intended to be run as a real cron job
every five minutes.
--disable (default if invoked as "stop")
Set project to DISABLED mode and stop daemons.
--status Show status.
See "start --help" for options.
Daemons:
These are continuously-running programs.
The process ID is recorded in the <pid_dir> directory
and the process is sent a SIGHUP in a DISABLE operation.
Both tasks and daemons can run on a different host (specified by <host>).
The default is the project's main host, which is specified in config.host
A daemon or task can be turned off by adding the <disabled/> element.
IMPLEMENTATION:
"Start" uses a file "run_state_HOST.xml" that records the enabled mode
and the last run time of periodic tasks on that host.
Looks like this:
@ -60,26 +48,18 @@ Looks like this:
1
</enabled>
</boinc>
Daemons:
Writes a PID to pid_HOST/command.pid.
Tasks:
Writes a timestamp to run_state_HOST.xml
to remember when the task was last run.
Both:
A lock file (pid_HOST/command.lock)
prevents tasks and daemons from being run
again when they are currently running.
'''
import boinc_path_config
from Boinc import boinc_project_path, configxml
import configxml
import sys, os, getopt, time, glob, fcntl, signal, socket
right_now = int(time.time())
verbose = os.isatty(sys.stdout.fileno())
verbose_daemon_run = 0
@ -92,7 +72,6 @@ prune_run_state = True #
is_main_host = False
local_hostname = ''
delegate_other_hosts_in_parallel = False
def get_host_list():
'''
Get a list of hosts mentioned in <host> elements of tasks and daemons
@ -107,11 +86,9 @@ def get_host_list():
if (host and (host not in hosts)):
hosts.append(host)
return hosts
def assign_task_defaults():
'''
Set the "host" and "disabled" attribute of tasks and daemons
'''
for task in config.tasks:
host = task.__dict__.get('host')
@ -131,90 +108,69 @@ def assign_task_defaults():
task.disabled = 1
else:
task.disabled = 0
def get_dir(name):
return config.config.__dict__.get(name+'_dir') or boinc_project_path.project_path(name)
return config.config.__dict__.get(name+'_dir') or os.path.join(project_dir,name)
def ensure_get_dir(name):
f = get_dir(name)
ensure_dir(f)
return f
def is_daemon(task):
'''returns true if task is a daemon'''
return task._name == 'daemon'
def get_task_command_basename(task):
return os.path.basename(task.cmd.split()[0])
def get_task_output_name(task):
return os.path.join(log_dir,
task.__dict__.get('output') or get_task_command_basename(task) + '.out')
def get_daemon_output_name(task):
return os.path.join(log_dir,
task.__dict__.get('output') or get_task_command_basename(task) + '.log')
def get_daemon_pid_name(task):
return os.path.join(pid_dir,
task.__dict__.get('pid_file') or get_task_command_basename(task) + '.pid')
def output_is_file(filename):
return filename and not filename.startswith('/dev/')
def get_task_lock_name(task):
return os.path.join(pid_dir,
task.__dict__.get('lock_file')
or (output_is_file(task.__dict__.get('output')) and task.__dict__.get('output')+'.lock')
or get_task_command_basename(task) + '.lock')
def ensure_dir(filename):
try:
os.mkdir(filename)
except OSError:
return
def timestamp(t = None):
return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(t or right_now))
def safe_read_int(filename):
try:
return int(open(filename).readline().strip())
except:
return 0
def get_stop_daemons_filename():
return boinc_project_path.project_path('stop_daemons')
return os.path.join(project_dir, 'stop_daemons')
def get_stop_sched_filename():
return boinc_project_path.project_path('stop_sched')
return os.path.join(project_dir, 'stop_sched')
def write_stop_daemons():
print >>open(get_stop_daemons_filename(),'w'), '<stop/>'
def remove_stop_daemons():
if os.path.exists(get_stop_daemons_filename()):
os.unlink(get_stop_daemons_filename())
def write_stop_sched():
print >>open(get_stop_sched_filename(),'w'), '<stop/>'
def remove_stop_sched():
if os.path.exists(get_stop_sched_filename()):
os.unlink(get_stop_sched_filename())
def safe_unlink(filename):
try:
os.unlink(filename)
except OSError, e:
print "Couldn't unlink %s:"%filename,e
def remove_cached_home_page():
path = boinc_project_path.project_path('html/cache/65/index.php')
path = os.path.join(project_dir, 'html/cache/65/index.php')
if os.path.exists(path):
print 'removing ' + path
safe_unlink(path)
def redirect(stdout='/dev/null', stderr=None, stdin='/dev/null'):
'''
Redirects stdio. The stdin, stdout, and stderr arguments are file names
@ -222,7 +178,6 @@ def redirect(stdout='/dev/null', stderr=None, stdin='/dev/null'):
in sys.stdin, sys.stdout, and sys.stderr. These arguments are optional
and default to /dev/null.
'''
si = open(stdin, 'r')
if not stderr: stderr = stdout
se = open(stderr, 'a+', 0)
@ -230,31 +185,26 @@ def redirect(stdout='/dev/null', stderr=None, stdin='/dev/null'):
so = se
else:
so = open(stdout, 'a+')
# Redirect standard file descriptors.
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
def fork():
''' fork with fork_delay '''
pid = os.fork()
if pid:
time.sleep(fork_delay)
return pid
def double_fork():
'''
This forks the current process into a daemon using a double-fork.
Returns 1 for parent, 0 for child.
See: http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012
'''
# this is necessary because otherwise any buffered output would get
# printed twice after the fork!
sys.stdout.flush()
# Do first fork.
try:
pid = fork()
@ -262,12 +212,10 @@ def double_fork():
except OSError, e:
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
sys.exit(1)
# Decouple from parent environment.
os.chdir("/")
os.umask(0)
os.setsid()
# Do second fork.
try:
pid = os.fork()
@ -276,17 +224,14 @@ def double_fork():
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
os._exit(1)
return 0
def write_pid_file(pidfile):
print >>open(pidfile,'w'), os.getpid()
def is_pid_running(pid):
try:
os.kill(pid,0)
return True
except OSError:
return False
# if we ever want to use this on windows see:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65203
# returns 0 on success, -1 on error
@ -299,32 +244,30 @@ def lock_file(filename):
return fcntl.lockf(file.fileno(), fcntl.LOCK_EX|fcntl.LOCK_NB)
except IOError:
return -1
def is_lock_file_locked(filename):
if lock_file(filename):
return True
else:
os.unlink(filename)
def contains_shell_characters(command):
return ('"' in command or "'" in command or
'\\' in command or '|' in command or
'>' in command)
def exec_command_string(command):
args = command.strip().split()
# set default path for program to <bin_dir>:
args[0] = os.path.realpath(os.path.join( bin_dir, args[0] ))
os.chdir(tmp_dir)
try:
if contains_shell_characters(command):
os.execl('/bin/sh', 'sh', '-c', ' '.join(args))
else:
os.execvp( args[0], args )
os.execv( args[0], args )
# on success we don't reach here
print >>sys.stderr, "Couldn't exec '%s'"%command
except OSError, e:
print >>sys.stderr, "Couldn't execute '%s':" %command, e
os._exit(1)
def lookup_task_run_state(task):
for run_state_task in run_state.tasks:
if run_state_task.cmd == task.cmd:
@ -335,7 +278,6 @@ def lookup_task_run_state(task):
run_state_task.last_run = 0
run_state_task.prunable = False
return run_state_task
def interpret_period(str):
''' "5 min" -> 5*60 ; "1 hour" -> 1*60*60; "2" -> 2*60 '''
s = str.strip().split()
@ -360,18 +302,13 @@ def interpret_period(str):
except ValueError:
pass
raise SystemExit('Invalid task period "%s"'%str)
def when_will_task_next_run(task, task_run_state):
return float(task_run_state.last_run) + interpret_period(task.period)
def time_to_run_task(task, task_run_state):
return (ignore_timestamps
or (right_now >= when_will_task_next_run(task,task_run_state)))
def update_task_timestamp(task_run_state):
task_run_state.last_run = right_now
def run_task(task):
'''Fork and exec command without stdout/err redirection'''
task_run_state = lookup_task_run_state(task)
@ -389,7 +326,6 @@ def run_task(task):
sys.exit(1)
redirect(get_task_output_name(task))
exec_command_string(task.cmd)
def run_daemon(task):
'''Double-fork and exec command with stdout/err redirection and pid writing'''
if double_fork() > 0: return
@ -405,7 +341,6 @@ def run_daemon(task):
print "[%s] Executing command:"%timestamp(), task.cmd
sys.stdout.flush()
exec_command_string(task.cmd)
def run_daemons():
found_any = False
if verbose: print "Starting daemons"
@ -419,7 +354,6 @@ def run_daemons():
continue
run_daemon(task)
return found_any
def run_tasks():
if verbose: print "Running tasks"
prepare_run_state_pruning()
@ -434,25 +368,19 @@ def run_tasks():
if run_state.enabled:
run_task(task)
do_prune_run_states()
def prepare_run_state_pruning():
for run_state_task in run_state.tasks:
run_state_task.prunable = True
def do_prune_run_states():
'''\
Delete tasks that have prunable==True (since we didn't touch them this run)
'''
if not prune_run_state:
return
for run_state_task in run_state.tasks:
if run_state_task.prunable:
print 'Deleting obsolete run_state task', run_state_task.cmd, '(last run %s)' %timestamp(float(run_state_task.last_run))
run_state.tasks.remove_node(run_state_task)
def stop_daemon(pid):
'''returns 1 if something stopped, else 0'''
try:
@ -464,7 +392,6 @@ def stop_daemon(pid):
if verbose:
print " Killed process", pid
return 1
def wait_for_process_to_end(pid):
'''if process is still running, wait for it to end.'''
if is_pid_running(pid):
@ -478,7 +405,6 @@ def wait_for_process_to_end(pid):
time.sleep(.5)
if verbose:
print ' ok'
def stop_daemons():
if verbose: print "Stopping all daemons"
write_stop_daemons()
@ -493,8 +419,6 @@ def stop_daemons():
if verbose:
if not count:
print " (No processes stopped)"
######################################################################
## command (action) functions:
def command_enable_start():
@ -511,7 +435,6 @@ def command_enable_start():
time.sleep(10) # give feeder time to start up and create shmem
remove_stop_sched()
remove_cached_home_page()
def command_cron_start():
if verbose: print "Verbose cron-start: status ==", (run_state.enabled and 'ENABLED' or 'DISABLED')
global verbose_daemon_run
@ -522,11 +445,9 @@ def command_cron_start():
else:
verbose_daemon_run = 1
run_tasks()
def command_tasks_start():
if verbose: print "Verbose cron-start: status ==", (run_state.enabled and 'ENABLED' or 'DISABLED')
run_tasks()
def command_disable_stop():
if verbose:
if run_state.enabled:
@ -538,7 +459,6 @@ def command_disable_stop():
# else scheduler will complained about no shared mem
stop_daemons()
remove_cached_home_page()
def command_status():
if run_state.enabled:
print "BOINC is ENABLED"
@ -563,12 +483,10 @@ def command_status():
lu = " locked "
else:
lu = "UNLOCKED"
if task.disabled:
d = " yes "
else:
d = " no "
print " %2d"%n, " %5d"%pid, rs, lu, d, " ", task.cmd
print
print "TASK last run period next run lock file disabled commandline"
@ -586,7 +504,6 @@ def command_status():
lu = " LOCKED "
else:
lu = "unlocked"
if task.disabled:
d = " yes "
else:
@ -594,13 +511,10 @@ def command_status():
print " %2d"%n, last_run.center(20), task.period.ljust(10), \
next_run.center(20), lu, d, " ", task.cmd
pass
def command_show_config():
# TODO: - all config items (e.g. where's logdir?)
raise SystemExit('TODO')
# ------------- main program begins here ---------------------
local_hostname = socket.gethostname()
local_hostname = local_hostname.split('.')[0]
# print 'local hostname: ', local_hostname
@ -614,26 +528,21 @@ elif program_name == 'status':
command = command_status
else:
command = None
def help():
print >>sys.stderr, "Syntax: %s [options] [command]" % sys.argv[0]
print >>sys.stderr, """ Starts or stops BOINC daemons and tasks.
Commands:
--enable (-e) Set BOINC to ENABLED mode and start daemons
--cron (-c) If ENABLED, start daemons and run tasks
Intended to be run from real cron every 5 min.
--cron-tasks If ENABLED run tasks only and do not start daemons
Intended to be run from real cron every 5 min.
--disable (-d) Set BOINC to DISABLED mode and stop daemons
--status (-s) Show status.
--show-config Show configuration
Options:
--quiet (-q) Operate quietly, even if STDOUT is a tty.
--verbose (-v) Operate verbosely, even if STDOUT is not a tty.
--config-file= Use specified file instead of program-path/../config.xml
--run-state-file= Use specified file instead of program-path/../run_state.xml
--fork-delay= Seconds to sleep between daemon forks instead of 0.1
@ -645,10 +554,9 @@ Options:
elif program_name == 'stop':
print >>sys.stderr, "Based on the invocation name as `stop', the default action is --disable."
sys.exit(1)
config_filename = boinc_project_path.config_xml_filename
run_state_filename = boinc_project_path.run_state_xml_filename
program_path = os.path.realpath(os.path.dirname(sys.argv[0]))
config_filename = os.path.realpath(os.path.join(program_path, '../config.xml'))
run_state_filename = os.path.realpath(os.path.join(program_path, '../run_state_'+local_hostname+'.xml'))
try:
opts, args = getopt.getopt(sys.argv[1:], 'cedskqvh?',
('enable', 'cron', 'cron-tasks', 'disable',
@ -693,42 +601,31 @@ for opt,v in opts:
elif opt == '--fork-delay':
fork_delay = v
else: assert(False)
if not command:
raise SystemExit('No command specified and script name is not "start", "stop", or "status"')
config = configxml.ConfigFile(config_filename).read()
run_state = configxml.RunStateFile(run_state_filename).read(failopen_ok = True)
os.chdir(boinc_project_path.project_path())
project_dir = os.path.realpath(config.config.__dict__.get('project_dir') or
os.path.join(program_path, '../'))
os.chdir(project_dir)
bin_dir = get_dir('bin')
cgi_bin_dir = get_dir('cgi_bin')
tmp_dir = ensure_get_dir('tmp_'+local_hostname)
log_dir = ensure_get_dir('log_'+local_hostname)
pid_dir = ensure_get_dir('pid_'+local_hostname)
is_main_host = config.config.host == local_hostname
if os.getenv('PATH'):
os.putenv('PATH', bin_dir + ':' + os.getenv('PATH'))
else:
os.putenv('PATH', bin_dir)
start_lockfile = os.path.join(pid_dir, 'start.lock.'+local_hostname)
if lock_file(start_lockfile):
print >>sys.stderr, "start is currently running!"
sys.exit(1)
assign_task_defaults()
apply(command)
run_state.write()
if is_main_host:
if delegate_other_hosts_in_parallel:
wait_mode = os.P_NOWAIT
else:
wait_mode = os.P_WAIT
other_hosts = get_host_list()
for host in other_hosts:
if host == local_hostname:
@ -738,5 +635,4 @@ if is_main_host:
remote_cmd += [ '-v' ]
print 'running ', ' '.join(remote_cmd)
os.spawnvp(wait_mode, remote_cmd[0], remote_cmd)
os.unlink(start_lockfile)