FAQ | This is a LIVE service | Changelog

Skip to content
Snippets Groups Projects
adjuster.py 489 KiB
Newer Older
   if i==-1: return html # TODO: what of HTML documents that lack <body> (and frameset), do we add one somewhere? (after any /head ??)
   i += 5 # after the "<body"
   attrsToAdd = B(attrsToAdd)
   for a in re.findall(B(r'[A-Za-z_0-9]+\='),attrsToAdd): attrs = attrs.replace(a,B("old")+a) # disable corresponding existing attributes (if anyone still uses them these days)
   return html[:i] + attrs + B(" ") + attrsToAdd + html[j:]
Silas S. Brown's avatar
Silas S. Brown committed
#@file: view-source.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# View-source support etc
# --------------------------------------------------

def ampEncode(t):
    if type(t)==bytes: return t.replace(B("&"),B("&amp;")).replace(B("<"),B("&lt;")).replace(B(">"),B("&gt;"))
    else: return t.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;")
def txt2html(t):
    t = ampEncode(t)
    if type(t)==bytes: return t.replace(B("\n"),B("<br>"))
    else: return t.replace("\n","<br>")
def ampDecode(t): return t.replace("&lt;","<").replace("&gt;",">").replace("&amp;","&") # called with unicode in Python 2 and str in Python 3, either way this should just work
# ampDecode is needed if passing text with entity names to Renderer below (which ampEncode's its result and we might want it to render & < > chars)
# (shouldn't need to cope with other named entities: find_text_in_HTML already processes all known ones in htmlentitydefs, and LXML also decodes all the ones it knows about)

Silas S. Brown's avatar
Silas S. Brown committed
#@file: image-render.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# Support old phones etc: CJK characters to images
# --------------------------------------------------

class Renderer:
    def __init__(self):
        self.renderFont = None
        self.hanziW,self.hanziH = 0,0
    def font(self):
        if not self.renderFont: # first time
            try: import ImageFont
            except:
                try: import PIL.ImageFont as ImageFont
                except: from PIL import ImageFont
            if options.renderFont: self.renderFont = ImageFont.truetype(options.renderFont, options.renderSize, encoding="unic")
            else: self.renderFont = ImageFont.load_default()
        return self.renderFont
    def getMarkup(self,unitext):
        i=0 ; import unicodedata
        width = 0 ; imgStrStart = -1
        ret = [] ; copyFrom = 0
        if options.renderWidth==0: doImgEnd=lambda:None
        else:
          def doImgEnd():
            if imgStrStart >= 0 and width <= options.renderWidth and len(ret) > imgStrStart + 1:
                ret.insert(imgStrStart,'<nobr>')
                ret.append('</nobr>')
        if options.renderBlocks: combining=lambda x:False
        else: combining=unicodedata.combining
        checkAhead = (options.renderNChar>1 or not options.renderBlocks)
        while i<len(unitext):
            if inRenderRange(unitext[i]) and not combining(unitext[i]): # (don't START the render with a combining character - that belongs to the character before)
                j = i+1
                if checkAhead:
                  charsDone = 1
                  while j<len(unitext) and ((charsDone < options.renderNChar and inRenderRange(unitext[j])) or combining(unitext[j])):
                    if not combining(unitext[j]): charsDone += 1
                    j += 1
                rep,w=self.getMarkup_inner(unitext[i:j])
                width += w
                if i>copyFrom: ret.append(ampEncode(unitext[copyFrom:i]))
                if imgStrStart==-1: imgStrStart = len(ret)
                ret.append(rep)
                copyFrom = i = j
            else:
                doImgEnd() ; i += 1
                width = 0 ; imgStrStart = -1
        doImgEnd()
        if i>copyFrom: ret.append(ampEncode(unitext[copyFrom:i]))
        return u"".join(ret)
    def getMarkup_inner(self,unitext):
        if options.renderBlocks and self.hanziW:
            w,h = self.hanziW*len(unitext), self.hanziH
        else:
            w,h = self.font().getsize(unitext)
            if options.renderBlocks:
        return ('<img src="%s%s" width=%s height=%s>' % (options.renderPath,imgEncode(unitext),w,h)), w # (%s is faster than %d apparently, and format strings are faster than ''.join)
    def getImage(self,uri):
        if not options.render or not uri.startswith(options.renderPath): return False
        try: import ImageDraw,Image
        except:
            try:
                import PIL.ImageDraw as ImageDraw
                import PIL.Image as Image
            except:
                from PIL import ImageDraw,Image
        try: text=imgDecode(uri[len(options.renderPath):])
        except: return False # invalid base64 = fall back to fetching the remote site
        size = self.font().getsize(text) # w,h
        if options.renderInvert: bkg,fill = 0,1
        else: bkg,fill = 1,0
        img=Image.new("1",size,bkg) # "1" is 1-bit
        ImageDraw.Draw(img).text((0, 0),text,font=self.font(),fill=fill)
        img.save(dat,options.renderFormat)
        return dat.getvalue()
Renderer=Renderer()

def create_inRenderRange_function(arg):
    # create the inRenderRange function up-front (don't re-parse the options every time it's called)
    global inRenderRange
    if type(arg)==type(""): arg=arg.split(',')
    arg = [tuple(int(x,16) for x in lh.split(':')) for lh in arg] # [(l,h), (l,h)]
    if len(arg)==1: # Only one range - try to make it fast
        l,h = arg[0]
        while l==0 or not unichr(l).strip(): l += 1 # NEVER send whitespace to the renderer, it will break all hope of proper word-wrapping in languages that use spaces. And chr(0) is used as separator.  (TODO: what if there's whitespace in the MIDDLE of one of the ranges?  but don't add the check to inRenderRange itself unless we've confirmed it might be needed)
        inRenderRange=lambda uc:(l <= ord(uc) <= h)
    elif arg: # More than one range
        inRenderRange=lambda uc:(ord(uc)>0 and uc.strip() and any(l <= ord(uc) <= h for l,h in arg))
    else: inRenderRange=lambda uc:(ord(uc) and uc.strip())

def imgEncode(unitext):
    # Encode unitext to something URL-safe, try to be efficient especially in small cases
    # Normally base64-encoded UTF-8 (output will be a multiple of 4 bytes)
    # but some single characters will be OK as-is, and 2 or 3 bytes could hex a unichr under U+1000
    # This function needs to be FAST - it can be called thousands of times during a page render
        o = ord(unitext)
        if o < 0x1000:
            # TODO: create_inRenderRange_function can also re-create this function to omit the above test if we don't have any ranges under 0x1000 ?  (but it should be quite quick)
            if unitext in letters+digits+"_.-": return unitext
            elif 0xf<ord(unitext): return hex(ord(unitext))[2:]
        elif o <= 0xFFFF: # (TODO: don't need that test if true for all our render ranges)
            # TODO: make this optional?  hex(ord(u))[-4:] is nearly 5x faster than b64encode(u.encode('utf-8')) in the case of 1 BMP character (it's faster than even just the .encode('utf-8') part), but result could also decode with base64, so we have to add an extra '_' byte to disambiguate, which adds to the traffic (although only a small amount compared to IMG markup anyway)
            return '_'+hex(o)[-4:]
    return S(base64.b64encode(unitext.encode('utf-8')))
def imgDecode(code):
    if len(code)==1: return code
    elif len(code) <= 3: return unichr(int(code,16))
    elif code.startswith("_"): return unichr(int(code[1:],16)) # (see TODO above)
    else: return base64.b64decode(code).decode('utf-8','replace')

Silas S. Brown's avatar
Silas S. Brown committed
#@file: ping.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# Support pinging watchdogs and Dynamic DNS services
# --------------------------------------------------
Silas S. Brown's avatar
Silas S. Brown committed
        if not (options.ip_query_url and options.ip_change_command): return
Silas S. Brown's avatar
Silas S. Brown committed
        # check for user:password@ in ip_query_url2
        global ip_query_url2,ip_query_url2_user,ip_query_url2_pwd,ip_url2_pwd_is_fname
        ip_query_url2 = options.ip_query_url2
        ip_query_url2_user=ip_query_url2_pwd=ip_url2_pwd_is_fname=None
        if ip_query_url2 and not ip_query_url2=="upnp":
            netloc = urlparse.urlparse(ip_query_url2).netloc
            if '@' in netloc:
                auth,rest = netloc.split('@',1)
                ip_query_url2 = ip_query_url2.replace(netloc,rest,1)
                ip_query_url2_user,ip_query_url2_pwd = auth.split(':',1)
                ip_url2_pwd_is_fname = os.path.isfile(ip_query_url2_pwd)
        # and start the updater
        IOLoopInstance().add_callback(lambda *args:self.queryIP())
    def queryLocalIP(self):
        # Queries ip_query_url2 (if set, and if we know current IP).  Depending on the response/situation, either passes control to queryIP (which sets the next timeout itself), or sets an ip_check_interval2 timeout.
        if not ip_query_url2 or not self.currentIP:
            return self.queryIP()
        debuglog("queryLocalIP")
Silas S. Brown's avatar
Silas S. Brown committed
        if ip_query_url2=="upnp":
            def run():
              try:
                miniupnpc.discover() # every time - it might have rebooted or changed
                miniupnpc.selectigd()
Silas S. Brown's avatar
Silas S. Brown committed
              except: addr = ""
              if addr == self.currentIP: IOLoopInstance().add_callback(lambda *args:(self.newIP(addr),IOLoopInstance().add_timeout(time.time()+options.ip_check_interval2,lambda *args:self.queryLocalIP()))) # (add_timeout doesn't always work when not from the IOLoop thread ??)
              else: IOLoopInstance().add_callback(self.queryIP)
Silas S. Brown's avatar
Silas S. Brown committed
            threading.Thread(target=run,args=()).start()
            return
Silas S. Brown's avatar
Silas S. Brown committed
            curlFinished()
            if r.error or not B(self.currentIP) in B(r.body):
                return self.queryIP()
            # otherwise it looks like the IP is unchanged:
            self.newIP(self.currentIP) # in case forceTime is up
            IOLoopInstance().add_timeout(time.time()+options.ip_check_interval2,lambda *args:self.queryLocalIP())
        if ip_query_url2_user:
            # some routers etc insist we send the non-auth'd request first, and the credentials only when prompted (that's what Lynx does with the -auth command line), TODO do we really need to do this every 60secs? (do it only if the other way gets an error??) but low-priority as this is all local-net stuff (and probably a dedicated link to the switch at that)
            if ip_url2_pwd_is_fname: pwd=open(ip_query_url2_pwd).read().strip() # re-read each time
            else: pwd = ip_query_url2_pwd
            callback = lambda r:(curlFinished(),doCallback(None,MyAsyncHTTPClient().fetch,handleResponse, ip_query_url2, auth_username=ip_query_url2_user,auth_password=pwd))
        doCallback(None,MyAsyncHTTPClient().fetch,callback,ip_query_url2)
        # Queries ip_query_url, and, after receiving a response (optionally via retries if ip_query_aggressive), sets a timeout to go back to queryLocalIP after ip_check_interval (not ip_check_interval2)
Silas S. Brown's avatar
Silas S. Brown committed
            curlFinished()
                if self.aggressive_mode:
                    logging.info("ip_query_url got response, stopping ip_query_aggressive")
                    self.aggressive_mode = False
            elif options.ip_query_aggressive:
                if not self.aggressive_mode:
                    logging.info("ip_query_url got error, starting ip_query_aggressive")
                    self.aggressive_mode = True
Silas S. Brown's avatar
Silas S. Brown committed
                    global pimote_may_need_override ; pimote_may_need_override = True # in case we're running that as well and it fails to detect the router issue via its own DNSRequest (happened once on Zyxel AMG1302-T11C: upnp stopped, connectivity stopped so got here, even DHCP stopped but somehow DNSRequest kept going without returning the internal-only IP or error, so better put this path in too)
            IOLoopInstance().add_timeout(time.time()+options.ip_check_interval,lambda *args:self.queryLocalIP())
        doCallback(None,MyAsyncHTTPClient().fetch,handleResponse,options.ip_query_url)
    def newIP(self,ip):
        debuglog("newIP "+ip)
        if ip==self.currentIP and (not options.ip_force_interval or time.time()<self.forceTime): return
        try: socket.inet_aton(ip) # IPv4 only
        except socket.error: # try IPv6
            try: socket.inet_pton(socket.AF_INET6,ip)
            except socket.error: return # illegal IP, maybe a temporary error from the server
        self.currentIP = ip
        if len(options.ip_change_command) < 50: logging.info("ip_change_command: "+cmd)
        else: logging.info("Running ip_change_command for "+ip)
        sp=subprocess.Popen(cmd,shell=True,stdin=subprocess.PIPE)
        self.forceTime=time.time()+options.ip_force_interval
        def retry(sp):
            triesLeft = options.ip_change_tries-1 # -ve if inf
            while triesLeft:
                if not sp.wait():
                    logging.info("ip_change_command succeeded for "+ip)
                    return
Silas S. Brown's avatar
Silas S. Brown committed
                logging.info("ip_change_command failed for "+ip+", retrying in "+str(options.ip_change_delay)+" seconds")
                time.sleep(options.ip_change_delay)
                sp=subprocess.Popen(cmd,shell=True,stdin=subprocess.PIPE)
                triesLeft -= 1
            if sp.wait(): logging.error("ip_change_command failed for "+ip)
            else: logging.info("ip_change_command succeeded for "+ip)
        threading.Thread(target=retry,args=(sp,)).start()

Silas S. Brown's avatar
Silas S. Brown committed
pimote_may_need_override=False
def pimote_thread():
    routerIP, ispDomain, internalResponse, deviceNo = options.pimote.split(',')
    try: deviceNo = int(deviceNo)
    except: pass # "all" etc is OK
    import DNS # apt-get install python-dns
    if deviceNo=="all": p17,p22,p23 = 1,1,0
    elif deviceNo==1:   p17,p22,p23 = 1,1,1
    elif deviceNo==2:   p17,p22,p23 = 1,0,1
    elif deviceNo==3:   p17,p22,p23 = 0,1,1
    elif deviceNo==4:   p17,p22,p23 = 0,0,1
    # TODO: unofficial 1 0 0 and 0 1 0 can be programmed (taking number of devices up to 6), + a 7th can be programmed on 000 but only for switch-on (as '0000' just resets the board; can switch off w. 'all').  Can program multiple switches to respond to same signal.
    else: raise Exception("Invalid Pi-mote device number "+repr(deviceNo))
    def t():
      lastOK = True
      helper_threads.append('PiMote')
Silas S. Brown's avatar
Silas S. Brown committed
      global pimote_may_need_override
Silas S. Brown's avatar
Silas S. Brown committed
        if pimote_may_need_override: ok=False
        else:
          try:
            r = DNS.DnsRequest(server=routerIP,timeout=5).req(name=ispDomain,qtype="A")
Silas S. Brown's avatar
Silas S. Brown committed
            ok = r.answers and not(any(i['data']==internalResponse for i in r.answers))
          except: ok = False
        if ok or lastOK:
            for i in xrange(30): # TODO: configurable?
Silas S. Brown's avatar
Silas S. Brown committed
                if options.pimote and not pimote_may_need_override: time.sleep(1)
        if pimote_may_need_override: reason = "" # will be evident from previous log line
        else: reason = " (%s lookup fail)" % ispDomain
        logging.info("PiMote: power-cycling the router"+reason)
        # Takes 2 minutes (because we have no direct way to verify
        # the sockets have received our signal, so just have to make
        # the hopefully-worst-case assumptions)
Silas S. Brown's avatar
Silas S. Brown committed
        def w(g,v):
            f = "/sys/class/gpio/gpio%d/value" % g
            try: open(f,'w').write(str(v)+"\n") # not sure if \n really needed
            except: logging.error("PiMote: unable to write to "+f)
        for OnOff in [0, 1]:
            w(25,0) # stop TX, just in case
            w(17,p17), w(22,p22), w(23,p23), w(27,OnOff)
            for Try in range(10): # sometimes the signal fails
                w(25,1) # start TX
                time.sleep(4)
                w(25,0) # stop TX
                time.sleep(1)
        time.sleep(199) # give it time to start up before we test it again, plus time to ssh in and fix if we're in a reboot cycle due to ISP being taken over and deleting their old domain or something
Silas S. Brown's avatar
Silas S. Brown committed
        pimote_may_need_override = False
      helper_threads.remove('PiMote')
    threading.Thread(target=t,args=()).start()
Silas S. Brown's avatar
Silas S. Brown committed
def open_upnp():
    if options.ip_query_url2=="upnp":
        global miniupnpc ; import miniupnpc # sudo pip install miniupnpc or apt-get install python-miniupnpc
        miniupnpc = miniupnpc.UPnP()
        miniupnpc.discoverdelay=200

Silas S. Brown's avatar
Silas S. Brown committed
watchdog = None
def openWatchdog():
    global watchdog
    watchdog = WatchdogPings()
Silas S. Brown's avatar
Silas S. Brown committed
    def __init__(self):
        if options.watchdog:
            self.wFile = open(options.watchdogDevice, 'wb')
Silas S. Brown's avatar
Silas S. Brown committed
        else: self.wFile = None
        # then call start() after privileges are dropped
    def start(self):
        if not self.wFile: return # no watchdog
Silas S. Brown's avatar
Silas S. Brown committed
        if options.watchdogWait: threading.Thread(target=self.separate_thread,args=()).start()
Silas S. Brown's avatar
Silas S. Brown committed
    def stop(self):
        if not self.wFile: return # no watchdog
        options.watchdog = 0 # tell any separate_thread() to stop (that thread is not counted in helper_threads)
Silas S. Brown's avatar
Silas S. Brown committed
        self.wFile.write('V') # this MIGHT be clean exit, IF the watchdog supports it (not all of them do, so it might not be advisable to use the watchdog option if you plan to stop the server without restarting it)
        self.wFile.close()
    def separate_thread(self): # version for watchdogWait
        # (does not adjust helper_threads / can't be "runaway")
        global watchdog_mainServerResponded # a flag.  Do NOT timestamp with time.time() - it can go wrong if NTP comes along and re-syncs the clock by a large amount
        def respond(*args):
            global watchdog_mainServerResponded
Silas S. Brown's avatar
Silas S. Brown committed
            debuglog("watchdogWait: responding",stillIdle=True)
            watchdog_mainServerResponded = True
        respond() ; stopped = 0 ; sleptSinceResponse = 0
        while options.watchdog:
            if watchdog_mainServerResponded:
                self.ping()
                if stopped:
                    logging.info("Main thread responded, restarting watchdog ping")
                    stopped = 0
                watchdog_mainServerResponded = False
                sleptSinceResponse = 0
            elif sleptSinceResponse < options.watchdogWait: self.ping() # keep waiting for it
Silas S. Brown's avatar
Silas S. Brown committed
                logging.error("Main thread unresponsive, stopping watchdog ping. lastDebugMsg: "+lastDebugMsg)
                stopped = 1 # but don't give up (it might respond just in time)
            time.sleep(options.watchdog)
            sleptSinceResponse += options.watchdog # "dead reckoning" to avoid time.time()
Silas S. Brown's avatar
Silas S. Brown committed
        if not options.watchdogWait: debuglog("pinging watchdog",logRepeats=False,stillIdle=True) # ONLY if run from MAIN thread, otherwise it might overwrite the real lastDebugMsg of where we were stuck
        self.wFile.write('a') ; self.wFile.flush()
        if not options.watchdogWait: # run from main thread
            IOLoopInstance().add_timeout(time.time()+options.watchdog,lambda *args:self.ping())
Silas S. Brown's avatar
Silas S. Brown committed
#@file: delegate.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# Support for "slow server delegates to fast server"
# (e.g. always-on Raspberry Pi + sometimes-on dev box)
# --------------------------------------------------

def FSU_set(new_FSU,interval):
    # sets new fasterServer_up state, and returns interval to next check
    global fasterServer_up
    fsu_old = fasterServer_up
    fasterServer_up = new_FSU
    if not fasterServer_up == fsu_old:
        if fasterServer_up: logging.info("fasterServer %s came up - forwarding traffic to it" % options.fasterServer)
        else: logging.info("fasterServer %s went down - handling traffic ourselves" % options.fasterServer)
Silas S. Brown's avatar
Silas S. Brown committed
    # debuglog("fasterServer_up="+repr(fasterServer_up)+" (err="+repr(r.error)+")",logRepeats=False,stillIdle=True)
    if fasterServer_up: return 1 # TODO: configurable? fallback if timeout when we try to connect to it as well?
    elif interval < 60: interval *= 2 # TODO: configurable?
    return interval
    def __init__(self):
        self.client = self.pendingClient = None
        self.count = 0
        self.interval=1
Silas S. Brown's avatar
Silas S. Brown committed
    def setup(self):
        if not options.fasterServer: return
        if not ':' in options.fasterServer: options.fasterServer += ":80" # needed for the new code
        logging.getLogger("tornado.general").disabled=1 # needed to suppress socket-level 'connection refused' messages from ping2 code in Tornado 3
        class NoConErrors:
            def filter(self,record): return not record.getMessage().startswith("Connect error on fd")
        logging.getLogger().addFilter(NoConErrors()) # ditto in Tornado 2 (which uses the root logger) (don't be tempted to combine this by setting tornado.general to a filter, as the message might change in future Tornado 3 releases)
         # TODO: might be bytes in the queue if this server somehow gets held up.  Could try readUntilClose(client,close,stream)
         if (self.client and self.count >= 2) or self.pendingClient: # it didn't call serverOK on 2 consecutive seconds (TODO: customizable?), or didn't connect within 1sec - give up
             try: self.pendingClient.close()
             except: pass
             try: self.client.close()
             except: pass
             self.pendingClient = self.client = None
             self.interval = FSU_set(False,self.interval)
             return IOLoopInstance().add_timeout(time.time()+self.interval,lambda *args:checkServer())
         elif self.client: self.count += 1
         else: # create new self.pendingClient
             server,port = options.fasterServer.rsplit(':',1)
             self.pendingClient = tornado.iostream.IOStream(socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0))
             def send_request(*args):
                 try:
                     self.pendingClient.write(B('GET /ping2 HTTP/1.0\r\nUser-Agent: ping\r\n\r\n'))
                     self.client = self.pendingClient
                     self.pendingClient = None
                     readUntilClose(self.client,lambda *args:True,lambda *args:self.serverOK())
             doCallback(None,self.pendingClient.connect,send_request,(server,int(port)))
         IOLoopInstance().add_timeout(time.time()+1,lambda *args:checkServer()) # check back in 1sec to see if it connected OK (should do if it's local)
     else: # old version - issue HTTP requests to /ping
        def callback(r):
            self.interval = FSU_set(not r.error,self.interval)
            if not fasterServer_up: self.client = None
            IOLoopInstance().add_timeout(time.time()+self.interval,lambda *args:checkServer())
Silas S. Brown's avatar
Silas S. Brown committed
        if not self.client:
            self.client=MyAsyncHTTPClient()
            curlFinished() # we won't count it here
        doCallback(None,self.client.fetch,callback,"http://"+options.fasterServer+"/ping",connect_timeout=1,request_timeout=1,user_agent="ping",use_gzip=False)
    def serverOK(self):
        # called when any chunk is available from the stream (normally once a second, but might catch up a few bytes if we've delayed for some reason)
        self.interval = FSU_set(True,0)
        self.count = 0
Silas S. Brown's avatar
Silas S. Brown committed
#@file: debug.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# Debugging and status dumps
# --------------------------------------------------

lastDebugMsg = "None" # for 'stopping watchdog ping'
Silas S. Brown's avatar
Silas S. Brown committed
def debuglog(msg,logRepeats=True,stillIdle=False):
Silas S. Brown's avatar
Silas S. Brown committed
    # This function *must* return None.
    global lastDebugMsg, profileIdle
Silas S. Brown's avatar
Silas S. Brown committed
    if not stillIdle: profileIdle = False
    if logRepeats or not msg==lastDebugMsg:
        if not options.logDebug: logging.debug(msg)
        elif options.background: logging.info(msg)
        else: sys.stderr.write(time.strftime("%X ")+msg+"\n")
Silas S. Brown's avatar
Silas S. Brown committed
    lastDebugMsg = msg ; global status_dump_requested
    if status_dump_requested:
        status_dump_requested = False
Silas S. Brown's avatar
Silas S. Brown committed
        showProfile(pjsOnly=True) # TODO: document that SIGUSR1 also does this? (but doesn't count reqsInFlight if profile wasn't turned on, + it happens on next debuglog call (and shown whether toggled on OR off, to allow rapid toggle just to show this))
Silas S. Brown's avatar
Silas S. Brown committed
def initLogDebug():
    if hasattr(signal,"SIGUSR1") and not wsgi_mode:
        signal.signal(signal.SIGUSR1, toggleLogDebug)
        if hasattr(signal,"SIGUSR2"):
            signal.signal(signal.SIGUSR2, requestStatusDump)
Silas S. Brown's avatar
Silas S. Brown committed
status_dump_requested = False
Silas S. Brown's avatar
Silas S. Brown committed
def toggleLogDebug(*args):
    "SIGUSR1 handler (see logDebug option help)"
    # Don't log anything from the signal handler
    # (as another log message might be in progress)
    # Just toggle the logDebug flag
    options.logDebug = not options.logDebug
Silas S. Brown's avatar
Silas S. Brown committed
    requestStatusDump()
def requestStatusDump(*args):
    "SIGUSR2 handler (requests status dump, currently from JS proxy only)" # TODO: document this (and that SIGUSR1 also calls it)
    global status_dump_requested ; status_dump_requested = True
Silas S. Brown's avatar
Silas S. Brown committed
#@file: end.py
Silas S. Brown's avatar
Silas S. Brown committed
# --------------------------------------------------
# And finally...
# --------------------------------------------------
Silas S. Brown's avatar
Silas S. Brown committed
else: setup_defined_globals() # for wrapper import