1### 2# Copyright (c) 2002-2005, Jeremiah Fincher 3# All rights reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions are met: 7# 8# * Redistributions of source code must retain the above copyright notice, 9# this list of conditions, and the following disclaimer. 10# * Redistributions in binary form must reproduce the above copyright notice, 11# this list of conditions, and the following disclaimer in the 12# documentation and/or other materials provided with the distribution. 13# * Neither the name of the author of this software nor the name of 14# contributors to this software may be used to endorse or promote products 15# derived from this software without specific prior written consent. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27# POSSIBILITY OF SUCH DAMAGE. 28### 29 30""" 31Module for general worldly stuff, like global variables and whatnot. 32""" 33 34import gc 35import os 36import sys 37import time 38import atexit 39import select 40import threading 41import multiprocessing 42 43import re 44 45from . import conf, drivers, ircutils, log, registry 46from .utils import minisix 47 48startedAt = time.time() # Just in case it doesn't get set later. 49 50starting = False 51 52mainThread = threading.currentThread() 53 54def isMainThread(): 55 return mainThread is threading.currentThread() 56 57threadsSpawned = 1 # Starts at one for the initial "thread." 58 59class SupyThread(threading.Thread, object): 60 def __init__(self, *args, **kwargs): 61 global threadsSpawned 62 threadsSpawned += 1 63 super(SupyThread, self).__init__(*args, **kwargs) 64 log.debug('Spawning thread %q.', self.getName()) 65 66processesSpawned = 1 # Starts at one for the initial process. 67class SupyProcess(multiprocessing.Process): 68 def __init__(self, *args, **kwargs): 69 global processesSpawned 70 processesSpawned += 1 71 super(SupyProcess, self).__init__(*args, **kwargs) 72 log.debug('Spawning process %q.', self.name) 73 74if sys.version_info[0:3] == (3, 3, 1) and hasattr(select, 'poll'): 75 # http://bugs.python.org/issue17707 76 import multiprocessing.connection 77 def _poll(fds, timeout): 78 if timeout is not None: 79 timeout = int(timeout * 1000) # timeout is in milliseconds 80 fd_map = {} 81 pollster = select.poll() 82 for fd in fds: 83 pollster.register(fd, select.POLLIN) 84 if hasattr(fd, 'fileno'): 85 fd_map[fd.fileno()] = fd 86 else: 87 fd_map[fd] = fd 88 ls = [] 89 for fd, event in pollster.poll(timeout): 90 if event & select.POLLNVAL: 91 raise ValueError('invalid file descriptor %i' % fd) 92 ls.append(fd_map[fd]) 93 return ls 94 multiprocessing.connection._poll = _poll 95 96 97commandsProcessed = 0 98 99ircs = [] # A list of all the IRCs. 100 101def getIrc(network): 102 """Returns Irc object of the given network. <network> is string and not case-sensitive.""" 103 network = network.lower() 104 for irc in ircs: 105 if irc.network.lower() == network: 106 return irc 107 return None 108 109def _flushUserData(): 110 userdataFilename = os.path.join(conf.supybot.directories.conf(), 111 'userdata.conf') 112 registry.close(conf.users, userdataFilename) 113 114flushers = [_flushUserData] # A periodic function will flush all these. 115 116registryFilename = None 117 118def flush(): 119 """Flushes all the registered flushers.""" 120 for (i, f) in enumerate(flushers): 121 try: 122 f() 123 except Exception: 124 log.exception('Uncaught exception in flusher #%s (%s):', i, f) 125 126def debugFlush(s=''): 127 if conf.supybot.debug.flushVeryOften(): 128 if s: 129 log.debug(s) 130 flush() 131 132def upkeep(): 133 """Does upkeep (like flushing, garbage collection, etc.)""" 134 # Just in case, let's clear the exception info. 135 try: 136 sys.exc_clear() 137 except AttributeError: 138 # Python 3 does not have sys.exc_clear. The except statement clears 139 # the info itself (and we've just entered an except statement) 140 pass 141 if os.name == 'nt': 142 try: 143 import msvcrt 144 msvcrt.heapmin() 145 except ImportError: 146 pass 147 except IOError: # Win98 148 pass 149 if conf.daemonized: 150 # If we're daemonized, sys.stdout has been replaced with a StringIO 151 # object, so let's see if anything's been printed, and if so, let's 152 # log.warning it (things shouldn't be printed, and we're more likely 153 # to get bug reports if we make it a warning). 154 if not hasattr(sys.stdout, 'getvalue'): 155 # Stupid twisted sometimes replaces our stdout with theirs, because 156 # "The Twisted Way Is The Right Way" (ha!). So we're stuck simply 157 # returning. 158 log.warning('Expected cStringIO as stdout, got %r.', sys.stdout) 159 return 160 s = sys.stdout.getvalue() 161 if s: 162 log.warning('Printed to stdout after daemonization: %s', s) 163 sys.stdout.seek(0) 164 sys.stdout.truncate() # Truncates to current offset. 165 s = sys.stderr.getvalue() 166 if s: 167 log.error('Printed to stderr after daemonization: %s', s) 168 sys.stderr.seek(0) 169 sys.stderr.truncate() # Truncates to current offset. 170 doFlush = conf.supybot.flush() and not starting 171 if doFlush: 172 flush() 173 # This is so registry._cache gets filled. 174 # This seems dumb, so we'll try not doing it anymore. 175 #if registryFilename is not None: 176 # registry.open(registryFilename) 177 if not dying: 178 if minisix.PY2: 179 log.debug('Regexp cache size: %s', len(re._cache)) 180 log.debug('Pattern cache size: %s', len(ircutils._patternCache)) 181 log.debug('HostmaskPatternEqual cache size: %s', 182 len(ircutils._hostmaskPatternEqualCache)) 183 #timestamp = log.timestamp() 184 if doFlush: 185 log.info('Flushers flushed and garbage collected.') 186 else: 187 log.info('Garbage collected.') 188 collected = gc.collect() 189 if gc.garbage: 190 log.warning('Noncollectable garbage (file this as a bug on SF.net): %s', 191 gc.garbage) 192 return collected 193 194def makeDriversDie(): 195 """Kills drivers.""" 196 log.info('Killing Driver objects.') 197 for driver in drivers._drivers.values(): 198 driver.die() 199 200def makeIrcsDie(): 201 """Kills Ircs.""" 202 log.info('Killing Irc objects.') 203 for irc in ircs[:]: 204 if not irc.zombie: 205 irc.die() 206 else: 207 log.debug('Not killing %s, it\'s already a zombie.', irc) 208 209def startDying(): 210 """Starts dying.""" 211 log.info('Shutdown initiated.') 212 global dying 213 dying = True 214 215def finished(): 216 log.info('Shutdown complete.') 217 218# These are in order; don't reorder them for cosmetic purposes. The order 219# in which they're registered is the reverse order in which they will run. 220atexit.register(finished) 221atexit.register(upkeep) 222atexit.register(makeIrcsDie) 223atexit.register(makeDriversDie) 224atexit.register(startDying) 225 226################################################## 227################################################## 228################################################## 229## Don't even *think* about messing with these. ## 230################################################## 231################################################## 232################################################## 233dying = False 234testing = False 235starting = False 236profiling = False 237documenting = False 238 239 240# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: 241