Module bussilab.cron

Functions

def cron(*,
quick_start: bool = False,
quick_start_skip_steps: int = 0,
quick_start_event: int = 0,
cron_file: str = '',
screen_cmd: str = 'screen',
screen_log: str = '',
no_screen: bool = True,
keep_ld_library_path: bool = True,
sockname: str = '(path):cron',
python_exec: str = '',
detach: bool = False,
period: int | None = None,
max_times: int | None = None,
unique: bool = False,
window: bool = False)
Expand source code
def cron(*,
         quick_start: bool = False,
         quick_start_skip_steps: int = 0,
         quick_start_event: int = 0,
         cron_file: str = "",
         screen_cmd: str = "screen",
         screen_log: str = "",
         no_screen: bool = True,
         keep_ld_library_path: bool = True,
         sockname: str = "(path):cron",
         python_exec: str = "",
         detach: bool = False,
         period: Optional[int] = None,
         max_times: Optional[int] = None,
         unique: bool = False,
         window: bool = False
         ):
    if not quick_start and quick_start_skip_steps>0:
        raise RuntimeError("quick_start_skip_steps can only be used with quick_start")
    if not quick_start and quick_start_event>0:
        raise RuntimeError("quick_start_event can only be used with quick_start")
    if no_screen:
        if "BUSSILAB_CRON_SCREEN_ARGS" in os.environ:
            env = json.loads(os.environ["BUSSILAB_CRON_SCREEN_ARGS"])
            if "rcfile" in env:
               os.unlink(env["rcfile"])
        if unique:
            raise RuntimeError("unique can only be used in screen mode")
        if window:
            raise RuntimeError("window can only be used in screen mode")
        print(_now(),"start")
        if max_times is not None:
            print(_now(),"remaining iterations:",max_times)
        counter=0
        if quick_start:
            print(_now(),"quick starting")
            if quick_start_skip_steps>0:
              print(_now(),"skipping",quick_start_skip_steps,"steps")
            r=_run(cron_file,_find_period(cron_file,period),quick_start_event,counter,quick_start_skip_steps)
            if isinstance(r,_reboot_now):
                print("exit now")
                return
            counter += 1
            if max_times is not None:
                if counter >= max_times:
                    print("maximum iterations done")
                    return
        while True:
            if max_times is not None:
                if counter >= max_times:
                    return
            s=_time_to_next_event(_find_period(cron_file,period))
            print(_now(),"Waiting " +"{:.3f}".format(s[0])+ " seconds for next scheduled event")
            time.sleep(s[0])
            r=_run(cron_file,_find_period(cron_file,period),s[1],counter)
            if isinstance(r,_reboot_now):
                return
            counter += 1
    else:

        # this dictionary is passed as an environment variable
        # it has two purposes:
        # - passing the arguments to further calls in case there is a reboot
        # - passing the path to the temporary screenrc file to be removed
        # the latter is only added when running a new screen socket (not window)
        env={ "arguments":{
              "python_exec" : python_exec,
              "screen_cmd"  : screen_cmd,
              "period"      : period,
              "cron_file"   : cron_file,
              "detach"      : detach,
              "max_times"   : max_times
            }}

        if python_exec == "":
            python_exec = sys.executable
        print(_now(),"python_exec:", python_exec)

        cmd = []
        cmd.extend(shlex.split(screen_cmd)) # allows screen_cmd to contain space separated options

        # in window mode, we do not launch a new socket.
        # these arguments are thus ignored
        if not window:
            sockname = _adjust_sockname(sockname,cron_file)

            # check if another process is already running
            if unique:
               cmd1=cmd.copy() # do not modity cmd
               cmd1.append("-ls")
               for l in subprocess.run(cmd1, # do not check errors here since screen -ls fails on MacOS
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                universal_newlines=True).stdout.split('\n'):
                   if "." + sockname +"\t" in l:
                       print("Another screen with socket name " + sockname + " is already present")
                       return

            # run in detaced mode
            if detach:
                cmd.append("-d")

            # write screen log
            if screen_log != "":
                cmd.append("-L")
                if screen_log != "screenlog.0":
                  cmd.append("-Logfile")
                  cmd.append(screen_log)

            # force a new screen
            cmd.append("-m")

            # create a named socket
            cmd.append("-S")
            cmd.append(sockname)

            # create a temporary screenrc file
            rcfile=tempfile.NamedTemporaryFile("w+t",delete=False)
            print(_screenrcfile,file=rcfile)
            rcfile.flush()

            # pass the temporary screenrc file as an argument
            cmd.append("-c")
            cmd.append(rcfile.name)

            # store the path so that the file can be cancelled later
            env["rcfile"]=rcfile.name

        cmd.append("-t")
        cmd.append("running")
        cmd.append("/usr/bin/env")
        cmd.append("BUSSILAB_CRON_SCREEN_ARGS=" + json.dumps(env))
        if keep_ld_library_path and 'LD_LIBRARY_PATH' in os.environ:
            cmd.append("LD_LIBRARY_PATH=" + os.environ["LD_LIBRARY_PATH"])
        cmd.extend(shlex.split(python_exec)) # allows python_exec to contain space separated options
        cmd.append("-m")
        cmd.append("bussilab")
        cmd.append("cron")
        cmd.append("--no-screen")
        if period is not None:
          cmd.append("--period")
          cmd.append(str(period))
        if len(cron_file)>0:
            cmd.append("--cron-file")
            cmd.append(cron_file)
        if quick_start:
            cmd.append("--quick-start")
            if quick_start_skip_steps>0:
              cmd.append("--quick-start-skip-steps")
              cmd.append(str(quick_start_skip_steps))
            if quick_start_event>0:
              cmd.append("--quick-start-event")
              cmd.append(str(quick_start_event))
        if max_times is not None:
            cmd.append("--max-times")
            cmd.append(str(max_times))

        print(_now(),"cmd:",cmd)

        try:
            ret=subprocess.call(cmd)
            if ret != 0:
                msg = "An error occurred."
                if screen_log !="" and screen_log != "screenlog.0":
                    msg += " Notice that some screen versions do not support a logfile with a name different from screenlog.0"
                raise RuntimeError(msg)
        except OSError:
            raise RuntimeError("Execution of failed. Perhaps '" + screen_cmd + "' command is not available on your system.")