better thread handling in GUI and more resiliant start/stop, thanks P.Pastori
This commit is contained in:
+9
-1
@@ -554,10 +554,14 @@ def wsgibase(environ, responder):
|
||||
http_response, request, environ, ticket)
|
||||
if not http_response:
|
||||
return wsgibase(new_environ, responder)
|
||||
|
||||
if global_settings.web2py_crontype == 'soft':
|
||||
# FIXME: calling softcron crondance in a new thread at every
|
||||
# request is not a good idea in a long running process
|
||||
cmd_opts = global_settings.cmd_options
|
||||
newcron.softcron(global_settings.applications_parent,
|
||||
apps=cmd_opts and cmd_opts.crontabs).start()
|
||||
|
||||
return http_response.to(responder, env=env)
|
||||
|
||||
|
||||
@@ -783,7 +787,11 @@ class HttpServer(object):
|
||||
"""
|
||||
stop cron and the web server
|
||||
"""
|
||||
newcron.stopcron()
|
||||
if global_settings.web2py_crontype == 'soft':
|
||||
try:
|
||||
newcron.stopcron()
|
||||
except:
|
||||
pass
|
||||
self.server.stop(stoplogging)
|
||||
try:
|
||||
os.unlink(self.pid_filename)
|
||||
|
||||
+14
-11
@@ -12,25 +12,30 @@ Cron-style interface
|
||||
|
||||
import threading
|
||||
import os
|
||||
from logging import getLogger, DEBUG
|
||||
from logging import getLogger
|
||||
import time
|
||||
import sched
|
||||
import sys
|
||||
import re
|
||||
from functools import reduce
|
||||
import datetime
|
||||
import shlex
|
||||
|
||||
import datetime
|
||||
from functools import reduce
|
||||
from gluon.settings import global_settings
|
||||
from gluon import fileutils
|
||||
from gluon._compat import to_bytes, pickle
|
||||
from pydal.contrib import portalocker
|
||||
from gluon.settings import global_settings
|
||||
|
||||
logger_name = 'web2py.cron'
|
||||
|
||||
|
||||
_cron_stopping = False
|
||||
|
||||
def reset():
|
||||
global _cron_stopping
|
||||
_cron_stopping = False
|
||||
|
||||
|
||||
_cron_subprocs_lock = threading.RLock()
|
||||
_cron_subprocs = []
|
||||
|
||||
@@ -75,9 +80,8 @@ class extcron(threading.Thread):
|
||||
self.apps = apps
|
||||
|
||||
def run(self):
|
||||
if not _cron_stopping:
|
||||
getLogger(logger_name).debug('external cron invocation')
|
||||
crondance(self.path, 'external', startup=False, apps=self.apps)
|
||||
getLogger(logger_name).debug('external cron invocation')
|
||||
crondance(self.path, 'external', startup=False, apps=self.apps)
|
||||
|
||||
|
||||
class hardcron(threading.Thread):
|
||||
@@ -119,7 +123,6 @@ class softcron(threading.Thread):
|
||||
crondance(self.path, 'soft', startup=False, apps=self.apps)
|
||||
|
||||
|
||||
# TODO: context manager
|
||||
class Token(object):
|
||||
|
||||
def __init__(self, path):
|
||||
@@ -140,7 +143,7 @@ class Token(object):
|
||||
stop == 0 if job started but did not yet complete
|
||||
if a cron job started within less than 60 seconds, acquire returns None
|
||||
if a cron job started before 60 seconds and did not stop,
|
||||
a warning is issue "Stale cron.master detected"
|
||||
a warning is issued ("Stale cron.master detected")
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
locktime = 59.5
|
||||
@@ -150,8 +153,8 @@ class Token(object):
|
||||
self.logger.warning('cron disabled because no file locking')
|
||||
return None
|
||||
self.master = fileutils.open_file(self.path, 'rb+')
|
||||
ret = None
|
||||
try:
|
||||
ret = None
|
||||
portalocker.lock(self.master, portalocker.LOCK_EX)
|
||||
try:
|
||||
(start, stop) = pickle.load(self.master)
|
||||
@@ -278,7 +281,7 @@ class cronlauncher(threading.Thread):
|
||||
if proc.returncode != 0:
|
||||
logger.warning('%r call returned code %s:\n%s\n%s',
|
||||
' '.join(self.cmd), proc.returncode, stdoutdata, stderrdata)
|
||||
elif logger.isEnabledFor(DEBUG):
|
||||
else:
|
||||
logger.debug('%r call returned success:\n%s',
|
||||
' '.join(self.cmd), stdoutdata)
|
||||
|
||||
|
||||
+153
-132
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# vim: set ts=4 sw=4 et ai:
|
||||
|
||||
"""
|
||||
| This file is part of the web2py Web Framework
|
||||
@@ -11,23 +12,26 @@ GUI widget and services start function
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
from gluon._compat import thread, xrange, PY2
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
import socket
|
||||
import signal
|
||||
import threading
|
||||
import math
|
||||
import logging
|
||||
import signal
|
||||
import getpass
|
||||
|
||||
from gluon import main, newcron
|
||||
from gluon.fileutils import read_file, create_welcome_w2p
|
||||
from gluon.console import console
|
||||
from gluon.settings import global_settings
|
||||
from gluon.shell import die, run, test
|
||||
from gluon.utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
|
||||
from gluon._compat import PY2, xrange
|
||||
from gluon.utils import (getipaddrinfo, is_loopback_ip_address,
|
||||
is_valid_ip_address)
|
||||
from gluon.console import is_appdir, console
|
||||
from gluon import newcron
|
||||
from gluon import main
|
||||
from gluon.settings import global_settings
|
||||
|
||||
|
||||
ProgramName = 'web2py Web Framework'
|
||||
@@ -119,24 +123,27 @@ class web2pyDialog(object):
|
||||
import tkinter
|
||||
from tkinter import messagebox
|
||||
|
||||
|
||||
bg_color = 'white'
|
||||
root.withdraw()
|
||||
|
||||
bg_color = 'white'
|
||||
self.root = tkinter.Toplevel(root, bg=bg_color)
|
||||
self.root.resizable(0, 0)
|
||||
self.root.title(ProgramName)
|
||||
|
||||
self.options = options
|
||||
self.scheduler_processes = {}
|
||||
self.menu = tkinter.Menu(self.root)
|
||||
servermenu = tkinter.Menu(self.menu, tearoff=0)
|
||||
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||
self.scheduler_processes_lock = threading.RLock()
|
||||
self.scheduler_processes = OrderedDict()
|
||||
|
||||
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
|
||||
if os.path.exists(iconphoto):
|
||||
img = tkinter.PhotoImage(file=iconphoto)
|
||||
self.root.tk.call('wm', 'iconphoto', self.root._w, img)
|
||||
|
||||
# Building the Menu
|
||||
self.menu = tkinter.Menu(self.root)
|
||||
servermenu = tkinter.Menu(self.menu, tearoff=0)
|
||||
|
||||
httplog = os.path.join(options.folder, options.log_filename)
|
||||
item = lambda: start_browser(httplog)
|
||||
servermenu.add_command(label='View httpserver.log',
|
||||
command=item)
|
||||
@@ -149,10 +156,9 @@ class web2pyDialog(object):
|
||||
self.pagesmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||
self.menu.add_cascade(label='Pages', menu=self.pagesmenu)
|
||||
|
||||
#scheduler menu
|
||||
self.schedmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||
self.menu.add_cascade(label='Scheduler', menu=self.schedmenu)
|
||||
#start and register schedulers from options
|
||||
# register and start schedulers
|
||||
self.update_schedulers(start=True)
|
||||
|
||||
helpmenu = tkinter.Menu(self.menu, tearoff=0)
|
||||
@@ -257,7 +263,7 @@ class web2pyDialog(object):
|
||||
sticky=sticky)
|
||||
|
||||
self.port_number = tkinter.Entry(self.root)
|
||||
self.port_number.insert(tkinter.END, self.options.port)
|
||||
self.port_number.insert(tkinter.END, options.port)
|
||||
self.port_number.grid(row=shift, column=2, sticky=sticky, pady=10)
|
||||
|
||||
# Password
|
||||
@@ -314,6 +320,26 @@ class web2pyDialog(object):
|
||||
self.tb = None
|
||||
|
||||
def update_schedulers(self, start=False):
|
||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||
available_apps = [
|
||||
arq for arq in os.listdir(applications_folder)
|
||||
if os.path.isdir(os.path.join(applications_folder, arq))
|
||||
]
|
||||
with self.scheduler_processes_lock:
|
||||
# reset the menu
|
||||
# since applications can disappear (be disinstalled) must
|
||||
# clear the menu (should use tkinter.END or tkinter.LAST)
|
||||
self.schedmenu.delete(0, 'end')
|
||||
for arq in available_apps:
|
||||
if arq not in self.scheduler_processes:
|
||||
item = lambda a = arq: self.try_start_scheduler(a)
|
||||
self.schedmenu.add_command(label="start %s" % arq,
|
||||
command=item)
|
||||
if arq in self.scheduler_processes:
|
||||
item = lambda a = arq: self.try_stop_scheduler(a)
|
||||
self.schedmenu.add_command(label="stop %s" % arq,
|
||||
command=item)
|
||||
|
||||
if start and self.options.with_scheduler and self.options.schedulers:
|
||||
# the widget takes care of starting the schedulers
|
||||
apps = [ag.split(':', 1)[0] for ag in self.options.schedulers]
|
||||
@@ -322,100 +348,90 @@ class web2pyDialog(object):
|
||||
for app in apps:
|
||||
self.try_start_scheduler(app)
|
||||
|
||||
# reset the menu
|
||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||
available_apps = [
|
||||
arq for arq in os.listdir(applications_folder)
|
||||
if os.path.isdir(os.path.join(applications_folder, arq))
|
||||
]
|
||||
self.schedmenu.delete(0, len(available_apps))
|
||||
|
||||
for arq in available_apps:
|
||||
if arq not in self.scheduler_processes:
|
||||
item = lambda u = arq: self.try_start_scheduler(u)
|
||||
self.schedmenu.add_command(label="start %s" % arq,
|
||||
command=item)
|
||||
if arq in self.scheduler_processes:
|
||||
item = lambda u = arq: self.try_stop_scheduler(u)
|
||||
self.schedmenu.add_command(label="stop %s" % arq,
|
||||
command=item)
|
||||
|
||||
def start_schedulers(self, app):
|
||||
try:
|
||||
from multiprocessing import Process
|
||||
except:
|
||||
sys.stderr.write('Sorry, -K only supported for Python 2.6+\n')
|
||||
return
|
||||
from multiprocessing import Process
|
||||
code = "from gluon.globals import current;current._scheduler.loop()"
|
||||
print('starting scheduler from widget for "%s"...' % app)
|
||||
args = (app, True, True, None, False, code, False, True)
|
||||
logging.getLogger().setLevel(self.options.log_level)
|
||||
p = Process(target=run, args=args)
|
||||
self.scheduler_processes[app] = p
|
||||
self.update_schedulers()
|
||||
print("Currently running %s scheduler processes" % (
|
||||
len(self.scheduler_processes)))
|
||||
with self.scheduler_processes_lock:
|
||||
self.scheduler_processes[app] = p
|
||||
self.update_schedulers()
|
||||
print("Currently running %s scheduler processes" % (
|
||||
len(self.scheduler_processes)))
|
||||
p.start()
|
||||
print("Processes started")
|
||||
|
||||
def try_stop_scheduler(self, app):
|
||||
if app in self.scheduler_processes:
|
||||
p = self.scheduler_processes[app]
|
||||
del self.scheduler_processes[app]
|
||||
def try_stop_scheduler(self, app, skip_update=False):
|
||||
p = None
|
||||
with self.scheduler_processes_lock:
|
||||
if app in self.scheduler_processes:
|
||||
p = self.scheduler_processes[app]
|
||||
del self.scheduler_processes[app]
|
||||
if p is not None:
|
||||
p.terminate()
|
||||
p.join()
|
||||
self.update_schedulers()
|
||||
if not skip_update:
|
||||
self.update_schedulers()
|
||||
|
||||
def try_start_scheduler(self, app):
|
||||
if app not in self.scheduler_processes:
|
||||
t = threading.Thread(target=self.start_schedulers, args=(app,))
|
||||
t = None
|
||||
with self.scheduler_processes_lock:
|
||||
if not is_appdir(self.options.folder, app):
|
||||
self.schedmenu.delete("start %s" % app)
|
||||
return
|
||||
if app not in self.scheduler_processes:
|
||||
t = threading.Thread(target=self.start_schedulers, args=(app,))
|
||||
if t is not None:
|
||||
t.start()
|
||||
|
||||
def checkTaskBar(self):
|
||||
""" Checks taskbar status """
|
||||
|
||||
if self.tb.status:
|
||||
if self.tb.status[0] == self.tb.EnumStatus.QUIT:
|
||||
tb = self.tb
|
||||
if tb.status:
|
||||
st0 = tb.status[0]
|
||||
EnumStatus = tb.EnumStatus
|
||||
if st0 == EnumStatus.QUIT:
|
||||
self.quit()
|
||||
elif self.tb.status[0] == self.tb.EnumStatus.TOGGLE:
|
||||
elif st0 == EnumStatus.TOGGLE:
|
||||
if self.root.state() == 'withdrawn':
|
||||
self.root.deiconify()
|
||||
else:
|
||||
self.root.withdraw()
|
||||
elif self.tb.status[0] == self.tb.EnumStatus.STOP:
|
||||
elif st0 == EnumStatus.STOP:
|
||||
self.stop()
|
||||
elif self.tb.status[0] == self.tb.EnumStatus.START:
|
||||
elif st0 == EnumStatus.START:
|
||||
self.start()
|
||||
elif self.tb.status[0] == self.tb.EnumStatus.RESTART:
|
||||
elif st0 == EnumStatus.RESTART:
|
||||
self.stop()
|
||||
self.start()
|
||||
del self.tb.status[0]
|
||||
del tb.status[0]
|
||||
|
||||
self.root.after(1000, self.checkTaskBar)
|
||||
|
||||
def update(self, text):
|
||||
""" Updates app text """
|
||||
|
||||
try:
|
||||
self.text.configure(state='normal')
|
||||
self.text.insert('end', text)
|
||||
self.text.configure(state='disabled')
|
||||
except:
|
||||
pass # ## this should only happen in case app is destroyed
|
||||
|
||||
def connect_pages(self):
|
||||
""" Connects pages """
|
||||
# reset the menu
|
||||
# reset the menu,
|
||||
# since applications can disappear (be disinstalled) must
|
||||
# clear the menu (should use tkinter.END or tkinter.LAST)
|
||||
self.pagesmenu.delete(0, 'end')
|
||||
applications_folder = os.path.join(self.options.folder, 'applications')
|
||||
available_apps = [
|
||||
arq for arq in os.listdir(applications_folder)
|
||||
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
|
||||
]
|
||||
self.pagesmenu.delete(0, len(available_apps))
|
||||
for arq in available_apps:
|
||||
url = self.url + arq
|
||||
item = lambda a = arq: self.try_start_browser(a)
|
||||
self.pagesmenu.add_command(
|
||||
label=url, command=lambda u=url: start_browser(u))
|
||||
label=url, command=item)
|
||||
|
||||
def try_start_browser(self, app):
|
||||
url = self.url + app
|
||||
if not is_appdir(self.options.folder, app):
|
||||
self.pagesmenu.delete(url)
|
||||
return
|
||||
start_browser(url)
|
||||
|
||||
def quit(self, justHide=False):
|
||||
""" Finishes the program execution """
|
||||
@@ -423,16 +439,20 @@ class web2pyDialog(object):
|
||||
self.root.withdraw()
|
||||
else:
|
||||
try:
|
||||
scheds = self.scheduler_processes.keys()
|
||||
with self.scheduler_processes_lock:
|
||||
scheds = list(self.scheduler_processes.keys())
|
||||
for t in scheds:
|
||||
self.try_stop_scheduler(t)
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
newcron.stopcron()
|
||||
self.try_stop_scheduler(t, skip_update=True)
|
||||
except:
|
||||
pass
|
||||
if self.options.with_cron and not self.options.soft_cron:
|
||||
# shutting down hardcron
|
||||
try:
|
||||
newcron.stopcron()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
# HttpServer.stop takes care of stopping softcron
|
||||
self.server.stop()
|
||||
except:
|
||||
pass
|
||||
@@ -450,34 +470,40 @@ class web2pyDialog(object):
|
||||
import tkMessageBox as messagebox
|
||||
else:
|
||||
from tkinter import messagebox
|
||||
|
||||
messagebox.showerror('web2py start server', message)
|
||||
|
||||
def start(self):
|
||||
""" Starts web2py server """
|
||||
|
||||
password = self.password.get()
|
||||
|
||||
if not password:
|
||||
self.error('no password, no web admin interface')
|
||||
|
||||
ip = self.selected_ip.get()
|
||||
|
||||
if not is_valid_ip_address(ip):
|
||||
return self.error('invalid host ip address')
|
||||
|
||||
try:
|
||||
port = int(self.port_number.get())
|
||||
except:
|
||||
except ValueError:
|
||||
return self.error('invalid port number')
|
||||
|
||||
if self.options.server_key and self.options.server_cert:
|
||||
proto = 'https'
|
||||
else:
|
||||
proto = 'http'
|
||||
|
||||
self.url = get_url(ip, proto=proto, port=port)
|
||||
|
||||
self.connect_pages()
|
||||
self.update_schedulers()
|
||||
|
||||
# softcron is stopped with HttpServer, thus if starting again
|
||||
# need to reset newcron._cron_stopping to re-enable cron
|
||||
if self.options.soft_cron:
|
||||
newcron.reset()
|
||||
|
||||
# FIXME: if the HttpServer is stopped, then started again,
|
||||
# does not start because of following error:
|
||||
# WARNING:Rocket.Errors.Port8000:Listener started when not ready.
|
||||
|
||||
self.button_start.configure(state='disabled')
|
||||
|
||||
try:
|
||||
@@ -502,7 +528,7 @@ class web2pyDialog(object):
|
||||
path=options.folder,
|
||||
interfaces=options.interfaces)
|
||||
|
||||
thread.start_new_thread(self.server.start, ())
|
||||
threading.Thread(target=self.server.start).start()
|
||||
except Exception as e:
|
||||
self.button_start.configure(state='normal')
|
||||
return self.error(str(e))
|
||||
@@ -514,11 +540,14 @@ class web2pyDialog(object):
|
||||
self.button_stop.configure(state='normal')
|
||||
|
||||
if not options.taskbar:
|
||||
thread.start_new_thread(
|
||||
start_browser, (get_url(ip, proto=proto, port=port), True))
|
||||
cpt = threading.Thread(target=start_browser,
|
||||
args=(get_url(ip, proto=proto, port=port), True))
|
||||
cpt.setDaemon(True)
|
||||
cpt.start()
|
||||
|
||||
self.password.configure(state='readonly')
|
||||
[ip.configure(state='disabled') for ip in self.ips.values()]
|
||||
for ip in self.ips.values():
|
||||
ip.configure(state='disabled')
|
||||
self.port_number.configure(state='readonly')
|
||||
|
||||
if self.tb:
|
||||
@@ -528,16 +557,15 @@ class web2pyDialog(object):
|
||||
for listener in self.server.server.listeners:
|
||||
if listener.ready:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
""" Stops web2py server """
|
||||
|
||||
self.button_start.configure(state='normal')
|
||||
self.button_stop.configure(state='disabled')
|
||||
self.password.configure(state='normal')
|
||||
[ip.configure(state='normal') for ip in self.ips.values()]
|
||||
for ip in self.ips.values():
|
||||
ip.configure(state='normal')
|
||||
self.port_number.configure(state='normal')
|
||||
self.server.stop()
|
||||
|
||||
@@ -546,48 +574,41 @@ class web2pyDialog(object):
|
||||
|
||||
def update_canvas(self):
|
||||
""" Updates canvas """
|
||||
|
||||
httplog = os.path.join(self.options.folder, self.options.log_filename)
|
||||
canvas = self.canvas
|
||||
try:
|
||||
t1 = os.path.getsize(httplog)
|
||||
except:
|
||||
self.canvas.after(1000, self.update_canvas)
|
||||
except OSError:
|
||||
canvas.after(1000, self.update_canvas)
|
||||
return
|
||||
|
||||
points = 400
|
||||
try:
|
||||
fp = open(httplog, 'r')
|
||||
fp.seek(self.t0)
|
||||
data = fp.read(t1 - self.t0)
|
||||
fp.close()
|
||||
value = self.p0[1:] + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
|
||||
self.p0 = value
|
||||
pvalues = self.p0[1:]
|
||||
with open(httplog, 'r') as fp:
|
||||
fp.seek(self.t0)
|
||||
data = fp.read(t1 - self.t0)
|
||||
self.p0 = pvalues + [10 + 90.0 / math.sqrt(1 + data.count('\n'))]
|
||||
|
||||
for i in xrange(len(self.p0) - 1):
|
||||
c = self.canvas.coords(self.q0[i])
|
||||
self.canvas.coords(self.q0[i],
|
||||
(c[0],
|
||||
self.p0[i],
|
||||
c[2],
|
||||
self.p0[i + 1]))
|
||||
for i in xrange(points - 1):
|
||||
c = canvas.coords(self.q0[i])
|
||||
canvas.coords(self.q0[i],
|
||||
(c[0], self.p0[i],
|
||||
c[2], self.p0[i + 1]))
|
||||
self.t0 = t1
|
||||
except BaseException:
|
||||
except AttributeError:
|
||||
self.t0 = time.time()
|
||||
self.t0 = t1
|
||||
self.p0 = [100] * 400
|
||||
self.q0 = [self.canvas.create_line(i, 100, i + 1, 100,
|
||||
fill='green') for i in xrange(len(self.p0) - 1)]
|
||||
self.p0 = [100] * points
|
||||
self.q0 = [canvas.create_line(i, 100, i + 1, 100,
|
||||
fill='green') for i in xrange(points - 1)]
|
||||
|
||||
self.canvas.after(1000, self.update_canvas)
|
||||
canvas.after(1000, self.update_canvas)
|
||||
|
||||
|
||||
def check_existent_app(options, appname):
|
||||
if os.path.isdir(os.path.join(options.folder, 'applications', appname)):
|
||||
return True
|
||||
|
||||
|
||||
def get_code_for_scheduler(app_groups, options):
|
||||
def get_code_for_scheduler(applications_parent, app_groups):
|
||||
app = app_groups[0]
|
||||
if not check_existent_app(options, app):
|
||||
if not is_appdir(applications_parent, app):
|
||||
print("Application '%s' doesn't exist, skipping" % app)
|
||||
return None, None
|
||||
code = 'from gluon.globals import current;'
|
||||
@@ -599,16 +620,10 @@ def get_code_for_scheduler(app_groups, options):
|
||||
|
||||
|
||||
def start_schedulers(options):
|
||||
try:
|
||||
from multiprocessing import Process
|
||||
except:
|
||||
sys.stderr.write('Sorry, -K only supported for Python 2.6+\n')
|
||||
return
|
||||
logging.getLogger().setLevel(options.log_level)
|
||||
|
||||
from multiprocessing import Process
|
||||
apps = [ag.split(':') for ag in options.schedulers]
|
||||
if not options.with_scheduler and len(apps) == 1:
|
||||
app, code = get_code_for_scheduler(apps[0], options)
|
||||
app, code = get_code_for_scheduler(options.folder, apps[0])
|
||||
if not app:
|
||||
return
|
||||
print('starting single-scheduler for "%s"...' % app)
|
||||
@@ -624,7 +639,7 @@ def start_schedulers(options):
|
||||
|
||||
processes = []
|
||||
for app_groups in apps:
|
||||
app, code = get_code_for_scheduler(app_groups, options)
|
||||
app, code = get_code_for_scheduler(options.folder, app_groups)
|
||||
if not app:
|
||||
continue
|
||||
print('starting scheduler for "%s"...' % app)
|
||||
@@ -652,6 +667,12 @@ def start():
|
||||
# get command line arguments
|
||||
options = console(version=ProgramVersion)
|
||||
|
||||
if options.with_scheduler or len(options.schedulers) > 1:
|
||||
try:
|
||||
from multiprocessing import Process
|
||||
except:
|
||||
die('Sorry, -K/--scheduler only supported for Python 2.6+')
|
||||
|
||||
if options.gae:
|
||||
# write app.yaml, gaehandler.py, and exit
|
||||
if not os.path.exists('app.yaml'):
|
||||
@@ -673,6 +694,7 @@ def start():
|
||||
|
||||
logger = logging.getLogger("web2py")
|
||||
logger.setLevel(options.log_level)
|
||||
logging.getLogger().setLevel(options.log_level) # root logger
|
||||
|
||||
# on new installation build the scaffolding app
|
||||
create_welcome_w2p()
|
||||
@@ -807,7 +829,7 @@ end tell
|
||||
options.password = getpass.getpass('choose a password:')
|
||||
|
||||
if not options.password and not options.no_banner:
|
||||
print('no password, disable admin interface')
|
||||
print('no password, no web admin interface')
|
||||
|
||||
# Use first interface IP and port if interfaces specified, since the
|
||||
# interfaces option overrides the IP (and related) options.
|
||||
@@ -883,5 +905,4 @@ end tell
|
||||
spt.join()
|
||||
except:
|
||||
logger.exception('error terminating schedulers')
|
||||
pass
|
||||
logging.shutdown()
|
||||
|
||||
Reference in New Issue
Block a user