项目概述
这个项目来自 USYD INFO1112 assignment 1。目标是开发一个能让程序在计划的时间运行的系统。类似于Unix里cron。
项目结构
这个项目有三个文件构成,runner.conf, runner.py, runstatus.py。runner.conf指定了在什么时间运行什么程序,runner.py会读取这个文件并在特定时间执行特定文件,runstatus.py可以获取程序的执行状态。
例如: 在runner.conf 里的 every Tuesday at 1100 run /bin/date意味着在每周二的上午11点执行/bin/date这个程序
runner.conf格式
程序支持三种格式命令在每周的特定时间执行every day[,day...] at HHMM[,HHMM...] run ..
例如:
every Tuesday at 1100 run /bin/date意味着在每周二的上午11点执行/bin/date这个程序
特殊情况:
如果今天是周一,上面这个例子将在这周二开始运行 如果今天是周三,上面这个例子将在下个周二开始运行在某天执行 on day[,day...] at HHMM[,HHMM...] run ..
例如: on Monday,Sunday at 1300 run /bin/echo hello world意味着在这周一和周天的下午一点运行/bin/echo hello world
特殊情况: 如果现在是周一的9点,上面这个例子将在下午一点以及周天的下午一点运行 如果现在是周二,上面这个例子将在周天的下午一点以及下周一的下午一点在当天执行 at HHMM[,HHMM...] run ..
例如:
at 0800 run /bin/cp /tmp/a /tmp/b意味着在今天早上八点运行/bin/cp /tmp/a /tmp/b
特殊情况:
如果现在是9点,上面这个例子将在明早八点运行
一些错误的格式: repeated day: every Tuesday,Thursday,Thursday at 1200,1100 run /bin/date
incorrect weekday: every Tues at 1100 run /bin/date
incorrect weekday: every tuesday at 1100 run /bin/date
incorrect time: every Tuesday at 11000 run /bin/date
incorrect time: on Tuesday at 2400 run /bin/date
incorrect time: on Tuesday at 1260 run /bin/date
incorrect time: on Tuesday at 12-0 run /bin/date
duplicate run time: on Monday at 1100,1200 run /bin/echo Hello on Monday at 1000,1100 run /bin/echo there
duplicate run time: at 0600,0600 run /bin/echo hello
no program to run: on Tuesday at 1100 run
invalid program to run: on Tuesday at 1100 run /what/huh?
invalid syntax on every Friday at 1100 run /bin/echo hello
runner.py
runner.py会在后台运行。开始运行时,会把process ID写进HOME/.runner.pid中并检查HOME/.runner.status是否存在。接着读取runner.conf,并把任务加到列表当中
runstatus.py
向runner.py发送信号,将程序的执行状态以标准输出的形式打印出来 三种状态: - 执行过了: ran date-time program-path parameters执行错误: error date-time program-path parameters
将要执行: will run atdate-time program-path parameters
用法
python3 runner.py & python3 runstatus.py
实现的细节
runner.py
主方法的伪代码实现:
解析runner.conf,生成task,加到tasks里。tasks是个按时间排序的队列
while tasks 不为空:
p = 取出tasks第一个task
时间差 = p.time_diff - 当前运行时间
sleep(时间差)
运行时间 += 时间差
运行程序
如果p.is_repeat:
生成新的task加入到tasks
基于面对对象思想,把每个要执行的任务当作一个对象,一起储存在一个任务列表里。
class Task:
def __init__(self, program, time_diff, is_repeat, status, record):
"""Parameter:program: the program going to runtime_diff: time difference from nowis_repeat: true if the commands contains the keyword 'every'status: can be 'will run at ', 'ran ', 'error 'record: the running time"""
self.program = program
self.time_diff = time_diff
self.is_repeat = is_repeat
self.status = status
self.record = record
由于exec会把当前程序替换掉,所以当我们想执行程序时,需要新建一个子进程,这里运用了fork和exec。
fork会创造一个子进程。主进程返回值是子进程的pid,子进程pid是0。如果pid是0,就执行,如果是-1说明fork出现错误了,其他的话,主进程应该等子进程结束。
由于父进程和子进程不共享内存,子进程发生的任何改变都不会对父进程有任何影响。但我们想知道子进程运行的结果有没有成功,这就要用到os.wait()。os.wait()返回一个tuple,如果tuple的第二个为0的话,子进程执行出现问题。
try:
pid = os.fork()
if pid == 0:
os.execv(pa,args)
raise Exception("exec error")
elif pid == -1:
raise Exception("fork error")
else:
child_status = os.wait()
if child_status[1]>>8 != 0:
raise Exception("non-executable program")
except:
# Have some errors when running the program
for t in ran_tasks:
if t == p:
t.status = "error "
if p.is_repeat == True:
delta = datetime.timedelta(seconds = p.time_diff+7*24*3600)
r = n + delta
new_prog = Task(p.program, p.time_diff + 7*24*3600, True, "will run at ",r.ctime())
tasks.append(new_prog)
tasks = sorted(tasks, key = lambda p: p.time_diff)
continue
程序在后台运行,接受到信号时,应该打印出所有程序的状态。
这涉及到两个程序间的信息交换有两种方法:共享内存。一个程序往一个文件里写,另一个程序读取这个文件。
pipe
这里用的是第一种方法,runstatus.py向runner.py发送信号,runner.py接受信号,并往.runner.status里写入东西。
def catchSignal(signum, frame):
with open(os.path.expanduser("~/.runner.status"), "w") as f:
for t1 in ran_tasks:
f.write(t1.status + t1.record + " "+ t1.program + "\n")
for t2 in tasks:
f.write(t2.status + t2.record + " "+ t2.program + "\n")
signal.signal(signal.SIGUSR1, catchSignal)
runstatus.py
向runner.py发送信号,等待runner.py写完任务状态,再把状态打印出来
try:
os.kill(pid, signal.SIGUSR1)
i = 0
try:
with open(os.path.expanduser("~/.runner.status"),"r") as f:
while(i < 5):
lines=f.readlines()
if len(lines)!= 0:
for l in lines:
print(l.strip("\n"))
file = open(os.path.expanduser("~/.runner.status"),"w")
file.close()
break
else:
time.sleep(1)
i+=1
if i==5:
print("status timeout")
sys.exit(1)
except:
print("runner.status file is missing.")
sys.exit(1)
except:
print("Failed to send the signal")
sys.exit(1)