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