Пользовательский планировщик, чтобы иметь последовательные + полупоследовательные сценарии с таймаутами / уничтожить переключатели?
Ниже приведен большой раздел моего кода, и в основном, если вы прокрутите вниз до execute_subscripts()
функция, которую вы можете видеть, у меня есть два сценария, работающие через execfile
которые работают красиво, они показывают prints
они экономят traceback
ошибки в файле ошибок.
Я пытаюсь превратить второй скрипт в тот, который не дожидается завершения, прежде чем перейти к следующему скрипту.
Как вы можете видеть, я пытался использовать subprocess
с Popen
запустить тихое, скрытое окно... однако оно, кажется, не запускается, и я понятия не имею, как использовать p.communicate()
функционировать правильно, чтобы получить tracebacks
и / или prints
,
Мне также... нужна помощь в создании какого-либо переключателя тайм-аута / уничтожения, так что если индекс либо через Popen
или execfile
Маршрут не завершается в течение 5 минут, чтобы пропустить его для этого цикла или повторить и пропустить, если сразу произойдет сбой.
Я понимаю, что я, вероятно, не должен использовать strftime
для времен.... однако эта часть работает хорошо для меня, поэтому я не вижу необходимости менять его.
from datetime import date, timedelta
from sched import scheduler
from time import time, sleep, strftime
import random
import traceback
import subprocess
s = scheduler(time, sleep)
random.seed()
def periodically(runtime, intsmall, intlarge, function):
## Get current time
currenttime = strftime('%H:%M:%S')
## If currenttime is anywhere between 23:40 and 23:50 then...
if currenttime > '23:40:00' and currenttime < '23:50:00':
## Open the error logging file as the variable "errors"
errors = open('MISC/ERROR(S).txt', 'a')
## Try to...
try:
## Call the clear subscript.
execfile("SUBSCRIPTS/CLEAR.py", {})
## On exception (fail)...
except Exception:
## Write the entire traceback error to file...
errors.write(traceback.format_exc() + '\n')
errors.write("\n\n")
## Close and exit the error logging file.
errors.close()
## Update time
currenttime = strftime('%H:%M:%S')
## Idle time
while currenttime >= '23:40:00' and currenttime <= '23:59:59' or currenttime >= '00:00:00' and currenttime <= '11:30:00':
## Update time
currenttime = strftime('%H:%M:%S')
print currenttime, "Idling..."
sleep(10)
## Update time
currenttime = strftime('%H:%M:%S')
## Initiate the scheduler.
runtime += random.randrange(intsmall, intlarge)
s.enter(runtime, 1, function, ())
s.run()
def execute_subscripts():
st = time()
print "Running..."
errors = open('MISC/ERROR(S).txt', 'a')
try:
execfile("SUBSCRIPTS/TESTSCRIPT.py", {})
except Exception:
errors.write(traceback.format_exc() + '\n')
errors.write("\n\n")
try:
execfile("SUBSCRIPTS/TEST.py", {})
except Exception:
errors.write(traceback.format_exc() + '\n')
errors.write("\n\n")
## subprocess.Popen(["pythonw", "SUBSCRIPTS/TEST.py", "0"], shell=True)
try:
execfile("SUBSCRIPTS/TESTSCRIPTTest.py", {})
except Exception:
errors.write(traceback.format_exc() + '\n')
errors.write("\n\n")
try:
execfile("SUBSCRIPTS/TESTTESTTEST.py", {})
except Exception:
errors.write(traceback.format_exc() + '\n')
errors.write("\n\n")
errors.close()
print """The whole routine took %.3f seconds""" % (time() - st)
while True:
periodically(50, -25, +90, execute_subscripts)
Любые идеи будут высоко ценится
Добавлена награда, надеюсь, кто-то знает, как этого добиться.
заранее спасибо
HYFLEX
Пример того, что я хочу, чтобы скрипт мог делать...
Подстрочный индекс 1 - Запустите в фоновом режиме, отправьте распечатки и ошибки из subscript1.py в main.py, не ждите его завершения, перейдите к подстрочному документу 2, время ожидания через 10 секунд (или как можно ближе к 10 секундам, или время ожидания после того, как все подписчики были вызваны.)
Подстрочный индекс 2 - Запуск в фоновом режиме, отправка распечаток и ошибок из subscript2.py в main.py, ожидание его завершения перед переходом к подстрочному документу 3, время ожидания через 10 секунд (или как можно ближе к 10 секундам, или время ожидания после всех подписки были названы.)
Подстрочный индекс 3 - Запуск в фоновом режиме, отправка распечаток и ошибок из subscript3.py в main.py, ожидание его завершения перед переходом к подстрочному тексту 4, время ожидания через 10 секунд (или как можно ближе к 10 секундам, или время ожидания после всех подписки были названы.)
Подстрочный индекс 4 - Запускать в фоновом режиме, отправлять распечатки и ошибки из subscript4.py в main.py, не дожидаясь его завершения, перейти к подстрочному 5, время ожидания через 10 секунд (или как можно ближе к 10 секундам, или тайм-аут после вызова всех подписчиков.)
Подстрочный индекс 5 - Запуск в фоновом режиме, отправка распечаток и ошибок из subscript5.py в main.py, ожидание его завершения перед переходом к следующему подскрипту (или в данном случае, конец цикла), тайм-аут через 10 секунд (или как можно ближе к 10 секунд, как мы можем, или тайм-аут после вызова всех подписчиков.)
Отпечатки и трассировка для shx2
[pid=9940] main running command: C:\Python27\python.exe SUB/subscript1.py (is_bg=False)
[pid=9940] main running command: C:\Python27\python.exe SUB/subscript1.py (is_bg=True)
Traceback (most recent call last):
File "C:\Test\main.py", line 21, in <module>
bg_proc1 = run_subscript(cmd, is_bg = True)
File "C:\Test\main.py", line 10, in run_subscript
return (cmd > sys.stdout) & BG # run in background
File "C:\Python27\lib\site-packages\plumbum\commands\modifiers.py", line 81, in __rand__
return Future(cmd.popen(), self.retcode)
File "C:\Python27\lib\site-packages\plumbum\commands\base.py", line 317, in popen
return self.cmd.popen(args, **kwargs)
File "C:\Python27\lib\site-packages\plumbum\commands\base.py", line 233, in popen
return self.cmd.popen(self.args + list(args), **kwargs)
File "C:\Python27\lib\site-packages\plumbum\machines\local.py", line 104, in popen
**kwargs)
File "C:\Python27\lib\site-packages\plumbum\machines\local.py", line 253, in _popen
stderr = stderr, cwd = str(cwd), env = env, **kwargs) # bufsize = 4096
File "C:\Python27\lib\subprocess.py", line 703, in __init__
errread, errwrite) = self._get_handles(stdin, stdout, stderr)
File "C:\Python27\lib\subprocess.py", line 851, in _get_handles
c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
UnsupportedOperation: fileno
РЕДАКТИРОВАТЬ:
| --> # Sub 1.py # --> Sequential with timeout --> Started: 11:30.00 --> Estimated Completion: 11:30.01 (1 Second) --> Timeout at 11:30:10 (10 Seconds) --> # Sub 2.py # --> Sequential with timeout --> Started: 11:30.02 (or after time Sub 1.py's timeout) --> Estimated Completion: 11:30.03 (1 Second) --> Timeout at 11:30:13 (10 Seconds) --> # Sub 3.py # --> Sequential with timeout --> Started: 11:30.04 (or after time Sub 2.py's timeout) --> Estimated Completion: 11:30.08 (3 Seconds) --> Timeout at 11:30:18 (10 Seconds)
| ^ ^
| | |
| -------------------------------------------------------------------------------------------------------------------------------------------------- |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
Scheduler -->|
| --> Sub 4.py --> Nonsequential with timeout --> Started: 11:30.00 --> Estimated Completion: 11:30.05 (5 Seconds) --> Timeout at 11:30:10 (15 Seconds)
|
| --> Sub 5.py --> Nonsequential with timeout --> Started: 11:30.00 --> Estimated Completion: 11:30.02 (2 Seconds) --> Timeout at 11:30:10 (10 Seconds)
|
| --> Sub 6.py --> Nonsequential with timeout --> Started: 11:30.00 --> Estimated Completion: 11:30.10 (10 Seconds) --> Timeout at 11:30:10 (25 Seconds)
Надеюсь, это поможет с визуальным представлением того, что я пытаюсь достичь
2 ответа
Если я понял, что вы пытаетесь сделать, subprocess.Popen() - это путь. Вот простой класс, который, я думаю, может предоставить все необходимые вам функции:
from time import sleep
import subprocess
import datetime
import os
class Worker:
def __init__(self, cmd):
print datetime.datetime.now(), ":: starting subprocess :: %s"%cmd
self.cmd = cmd
self.log = "[running :: %s]\n"%cmd
self.subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.start_time = datetime.datetime.now()
def wait_to_finish(self, timeout_seconds = None):
while True:
retcode = self.subp.poll()
if retcode is not None:
self.get_process_output()
self.log += "\n[subprocess finished, return code: %d]\n"%retcode
print datetime.datetime.now(), ":: subprocess %s exited, retcode=%d"%(self.cmd, retcode)
return
else:
# process hasn't finished yet
sleep(1)
if timeout_seconds is not None:
cur_time = datetime.datetime.now()
if (cur_time - self.start_time).seconds > timeout_seconds:
print datetime.datetime.now(), ":: subprocess %s :: killing after %d seconds"%(self.cmd, timeout_seconds)
self.kill()
return
def still_running(self):
return (self.subp.poll() is None)
def kill(self):
self.subp.terminate()
self.get_process_output()
self.log += "\n[subprocess killed by explicit request]\n"
return
def get_process_output(self):
out, err = self.subp.communicate()
self.log += out
self.log += err
Вы даете команду, и класс запускает ее в фоновом режиме. Затем вы можете подождать, пока он закончится, с необязательным таймаутом (отсчитывается с момента запуска процесса). Вы можете получить вывод процесса и при необходимости явно убить процесс.
Вот простой пример, демонстрирующий его функциональность:
# Start two subprocesses in the background
worker1 = Worker([r'c:\python26\python.exe', 'sub1.py'])
worker2 = Worker([r'c:\python26\python.exe', 'sub2.py'])
# Wait for both to finish, kill after 10 seconds timeout
worker1.wait_to_finish(timeout_seconds = 10)
worker2.wait_to_finish(timeout_seconds = 10)
# Start another subprocess giving it 5 seconds to finish
worker3 = Worker([r'c:\python26\python.exe', 'sub3.py'])
worker3.wait_to_finish(timeout_seconds = 5)
print "----LOG1----\n" + worker1.log
print "----LOG2----\n" + worker2.log
print "----LOG3----\n" + worker3.log
sub1.py:
from time import sleep
print "sub1 output: start"
sleep(5)
print "sub1 output: finish"
sub2.py:
print "sub2 output: start"
erroneous_command()
sub3.py:
from time import sleep
import sys
print "sub3 output: start, sleeping 15 sec"
sys.stdout.flush()
sleep(15)
print "sub3 output: finish"
Вот вывод:
2013-11-06 15:31:17.296000 :: starting subprocess :: ['c:\\python26\\python.exe', 'sub1.py']
2013-11-06 15:31:17.300000 :: starting subprocess :: ['c:\\python26\\python.exe', 'sub2.py']
2013-11-06 15:31:23.306000 :: subprocess ['c:\\python26\\python.exe', 'sub1.py'] exited, retcode=0
2013-11-06 15:31:23.309000 :: subprocess ['c:\\python26\\python.exe', 'sub2.py'] exited, retcode=1
2013-11-06 15:31:23.310000 :: starting subprocess :: ['c:\\python26\\python.exe', 'sub3.py']
2013-11-06 15:31:29.314000 :: subprocess ['c:\\python26\\python.exe', 'sub3.py'] :: killing after 5 seconds
----LOG1----
[running :: ['c:\\python26\\python.exe', 'sub1.py']]
sub1 output: start
sub1 output: finish
[subprocess finished, return code: 0]
----LOG2----
[running :: ['c:\\python26\\python.exe', 'sub2.py']]
sub2 output: start
Traceback (most recent call last):
File "sub2.py", line 2, in <module>
erroneous_command()
NameError: name 'erroneous_command' is not defined
[subprocess finished, return code: 1]
----LOG3----
[running :: ['c:\\python26\\python.exe', 'sub3.py']]
sub3 output: start, sleeping 15 sec
[subprocess killed by explicit request]
Что касается реализации планирования, я могу предложить несколько вариантов, но выбор действительно зависит от вашей задачи:
1) Если вы можете указать точное планирование в любой момент времени, то вы можете реализовать полностью синхронный планировщик:
while True:
# check time
# check currently running processes :: workerX.still_running()
# -> if some are past their timeout, kill them workerX.kill()
# start new subprocesses according to your scheduling logic
sleep(1)
2) Если у вас есть несколько четко определенных последовательностей сценариев, которые вы хотите просто "запускать и забывать" каждые 10 секунд, то поместите каждую последовательность в свой собственный сценарий.py (с помощью "import Worker") и запускайте все последовательности каждые 10 секунд, также периодически проверяя, какие последовательности вышли, чтобы собрать свои журналы.
3) Если ваши последовательности определены динамически, и вы предпочитаете подход "запусти и забудь", то потоки будут лучшим подходом.
Как вы уже указали в своем вопросе, вы на самом деле задаете два разных вопроса (работа в фоновом режиме и применение тайм-аута). К счастью, короткий ответ для одного и того же:
Используйте Plumbum!
Plumbum значительно упрощает элементы сценария Python, подобные сценариям оболочки, и, среди прочего, обеспечивает чистые интерфейсы для выполнения команд в фоновом режиме и для принудительного истечения времени ожидания.
Ниже приведен пример использования свинца для этого.
В этом примере все подпроцессы будут запускать один и тот же сценарий - subscript1.py
, Он печатает, спит, иногда терпит неудачу, случайно.
subscript1.py
import os, sys, time, random
print '[pid=%s] STARTING %s' % (os.getpid(), sys.argv[0])
for i in range(3):
t = random.randint(1,5)
print '[pid=%s] sleeping for %s seconds' % (os.getpid(), t)
time.sleep(t)
# fail randomly
if t == 5:
raise RuntimeError('random error...')
print '[pid=%s] DONE %s' % (os.getpid(), sys.argv[0])
Теперь основной сценарий ниже, main.py
, демонстрирует, как запускать подпроцессы, на переднем плане и в фоновом режиме, с таймаутом и без него, ждать завершения фоновых процессов и обрабатывать ошибки и тайм-ауты субпроцесса.
main.py
import os, sys, time
from plumbum import FG, BG, ProcessExecutionError, ProcessTimedOut
from plumbum.cmd import python
cmd = python['subscript1.py'] # create the command to run (several times)
def run_subscript(cmd, is_bg = False):
print '[pid=%s] main running command: %s (is_bg=%s)' % (os.getpid(), cmd, is_bg)
if is_bg:
return (cmd > sys.stdout) & BG # run in background
else:
try:
return cmd & FG # run in foreground
except ProcessExecutionError, e:
print >>sys.stderr, e
# run a process in the foreground
run_subscript(cmd, is_bg = False)
# run two processes in the background, and one in the foreground
bg_proc1 = run_subscript(cmd, is_bg = True)
time.sleep(1)
bg_proc2 = run_subscript(cmd, is_bg = True)
time.sleep(1)
run_subscript(cmd, is_bg = False)
# wait for the background processes to finish
for bg_proc in ( bg_proc1, bg_proc2 ):
try:
bg_proc.wait()
except ProcessExecutionError, e:
print >>sys.stderr, e
# run a foreground process, which will time out
print '[pid=%s] main running command: %s (will time out)' % (os.getpid(), cmd)
try:
cmd.run(timeout = 2)
except ProcessTimedOut, e:
# command timed out
print >>sys.stderr, e
except ProcessExecutionError, e:
# command failed (but did not time out)
print >>sys.stderr, e
Выход:
% python main.py
[pid=77311] main running command: /usr/local/bin/python subscript1.py (is_bg=False)
[pid=77314] STARTING subscript1.py
[pid=77314] sleeping for 1 seconds
[pid=77314] sleeping for 5 seconds
[pid=77314] sleeping for 3 seconds
[pid=77314] DONE subscript1.py
[pid=77311] main running command: /usr/local/bin/python subscript1.py (is_bg=True)
[pid=77316] STARTING subscript1.py
[pid=77316] sleeping for 5 seconds
[pid=77311] main running command: /usr/local/bin/python subscript1.py (is_bg=True)
[pid=77317] STARTING subscript1.py
[pid=77317] sleeping for 1 seconds
[pid=77311] main running command: /usr/local/bin/python subscript1.py (is_bg=False)
[pid=77317] sleeping for 5 seconds
[pid=77318] STARTING subscript1.py
[pid=77318] sleeping for 5 seconds
[pid=77316] sleeping for 2 seconds
[pid=77316] sleeping for 4 seconds
[pid=77317] sleeping for 5 seconds
[pid=77318] sleeping for 2 seconds
[pid=77318] sleeping for 3 seconds
[pid=77316] DONE subscript1.py
[pid=77318] DONE subscript1.py
Command line: ['/usr/local/bin/python', 'subscript1.py']
Exit code: 1
Stderr: | Traceback (most recent call last):
| File "subscript1.py", line 13, in <module>
| raise RuntimeError('random error...')
| RuntimeError: random error...
[pid=77311] main running command: /usr/local/bin/python subscript1.py (will time out)
('Process did not terminate within 2 seconds', ['/usr/local/bin/python', 'subscript1.py'])
РЕДАКТИРОВАТЬ:
Теперь я понимаю, что мой пример кода не демонстрирует выполнение команды в фоновом режиме и применение тайм-аута для нее. Для этого просто используйте cmd.bgrun(...)
вместо cmd.run(...)
,
Полученная ошибка связана с перенаправлением и должна быть связана с тем, что вы работаете в Windows. Это либо проблема совместимости plumbum в Windows, либо мой код может быть не идеальным, т. Е. Может быть другой способ использовать plumbum для его работы. К сожалению, у меня нет компьютера с Windows, чтобы проверить его...
Надеюсь, это поможет.