Ticket #761: fix-twistd-exit-code.4.patch

File fix-twistd-exit-code.4.patch, 10.4 KB (added by Martin Gergov, 7 years ago)

Support for Windows

  • twisted/scripts/twistd.py

     
    88@author: Christopher Armstrong
    99"""
    1010
     11import signal
     12import os
    1113from twisted.application import app
     14from twisted.internet import reactor
     15from twisted.python import log
    1216
    1317from twisted.python.runtime import platformType
    1418if platformType == "win32":
     
    1822    from twisted.scripts._twistd_unix import ServerOptions, \
    1923        UnixApplicationRunner as _SomeApplicationRunner
    2024
     25class TwistdApplicationRunner(_SomeApplicationRunner):
     26    """
     27    @ivar _exitStatus: preserves exit status of twistd.
     28    """
     29    def __init__(self, config):
     30        self._exitStatus = 0
     31        _SomeApplicationRunner.__init__(self, config)
     32        signal.signal(signal.SIGINT, self.sigInt)
     33        signal.signal(signal.SIGTERM, self.sigTerm)
    2134
     35        # Catch Ctrl-Break in windows
     36        if hasattr(signal, "SIGBREAK"):
     37            signal.signal(signal.SIGBREAK, self.sigBreak)
     38
     39    def sigInt(self, *args):
     40        """Handle a SIGINT interrupt.
     41        """
     42        log.msg("Received SIGINT, shutting down.")
     43        reactor.callFromThread(reactor.stop)
     44        self._exitStatus = signal.SIGINT
     45
     46    def sigBreak(self, *args):
     47        """Handle a SIGBREAK interrupt.
     48        """
     49        log.msg("Received SIGBREAK, shutting down.")
     50        reactor.callFromThread(reactor.stop)
     51        self._exitStatus = signal.SIGBREAK
     52
     53    def sigTerm(self, *args):
     54        """Handle a SIGTERM interrupt.
     55        """
     56        log.msg("Received SIGTERM, shutting down.")
     57        reactor.callFromThread(reactor.stop)
     58        self._exitStatus = signal.SIGTERM
     59
    2260def runApp(config):
    23     _SomeApplicationRunner(config).run()
     61    app = TwistdApplicationRunner(config)
     62    app.run()
     63    if app._exitStatus:
     64        signal.signal(app._exitStatus, signal.SIG_DFL)
     65        if platformType == "win32":
     66            #The application terminated as a result of a CTRL+C.
     67            #for SIGINT and SIGBREAK (572)
     68            #The process terminated unexpectedly for SIGTERM (1067)
     69            replacements = {signal.SIGINT: 572, signal.SIGBREAK: 572,
     70                            signal.SIGTERM: 1067}
     71            app._exitStatus = replacements[app._exitStatus]
     72        os.kill(os.getpid(), app._exitStatus)
    2473
    25 
    2674def run():
    2775    app.run(runApp, ServerOptions)
    2876
    29 
    3077__all__ = ['run', 'runApp']
  • twisted/scripts/test/test_twistd.py

     
     1import os
     2import signal
     3from twisted.trial import unittest
     4from twisted.test.test_process import MockOS, MockSignal
     5from twisted.scripts import twistd
     6from twisted.python import runtime
     7
     8
     9class TwistdSignalHandlingTests(unittest.TestCase):
     10
     11    def patch_functions(self, mockOS, mockSignal):
     12        """
     13        Replace os and signal functions with mock ones
     14        """
     15        self.oldKill, self.oldSignal = twistd.os.kill, twistd.signal.signal
     16        twistd.os.kill = mockOS.kill
     17        twistd.signal.signal = mockSignal.signal
     18
     19    def cleanup_functions(self):
     20        """Get the old functions back"""
     21        twistd.os.kill = self.oldKill
     22        twistd.signal.signal = self.oldSignal
     23
     24    def test_overwriteSIGINTHandler(self):
     25        """
     26        Install new sigint handler.
     27        """
     28        mockOS = MockOS()
     29        mockSignal = MockSignal()
     30        self.patch_functions(mockOS, mockSignal)
     31        runner = twistd.TwistdApplicationRunner({"nodaemon": True,
     32                                                 "logfile": "-"})
     33        self.cleanup_functions()
     34        self.assertIn((signal.SIGINT, runner.sigInt), mockSignal.signals)
     35
     36    def test_overwriteSIGTERMHandler(self):
     37        """
     38        Install new sigterm handler.
     39        """
     40        mockOS = MockOS()
     41        mockSignal = MockSignal()
     42        self.patch_functions(mockOS, mockSignal)
     43        runner = twistd.TwistdApplicationRunner({"nodaemon": True,
     44                                                 "logfile": "-"})
     45        self.cleanup_functions()
     46        self.assertIn((signal.SIGTERM, runner.sigTerm), mockSignal.signals)
     47
     48    def test_overwriteSIGBREAKHandler(self):
     49        """
     50        Install new sigbreak handler.
     51        """
     52        mockOS = MockOS()
     53        mockSignal = MockSignal()
     54        self.patch_functions(mockOS, mockSignal)
     55        runner = twistd.TwistdApplicationRunner({"nodaemon": True,
     56                                                 "logfile": "-"})
     57        self.cleanup_functions()
     58        self.assertIn((signal.SIGBREAK, runner.sigBreak), mockSignal.signals)
     59
     60    def runTwistdInFakeEnviroment(self, replaceRun):
     61        """
     62        Run twistd with replaced os, signal and reactor.stop
     63        functions, also replaced run method
     64        @param replaceRun: function to replace run.
     65        """
     66        mockOS = MockOS()
     67        mockSignal = MockSignal()
     68        self.patch_functions(mockOS, mockSignal)
     69        oldRun = twistd.TwistdApplicationRunner.run
     70        twistd.TwistdApplicationRunner.run = replaceRun
     71        oldStop = twistd.reactor.stop
     72        #don't really stop the reactor
     73        twistd.reactor.stop = lambda: 0
     74        twistd.runApp({"nodaemon": True, "logfile": "-"})
     75        twistd.reactor.stop = oldStop
     76        twistd.TwistdApplicationRunner.run = oldRun
     77        self.cleanup_functions()
     78
     79        return mockOS, mockSignal
     80
     81    def getExitStatuses(self):
     82        """
     83        Returns appropriate reaction to SIGINT, SIGBREAK, SIGTERM in
     84        that order.
     85        """
     86        if runtime.platform.isWindows():
     87            return (572, 572, 1067)
     88        else:
     89            return (signal.SIGINT, -1, signal.SIGTERM)
     90
     91    def test_exitStatusAfterKillWithSIGINT(self):
     92        """
     93        Assert appropriate exit status after sending SIGINT.
     94        """
     95       
     96        mockOS, mockSignal = self.runTwistdInFakeEnviroment(
     97            twistd.TwistdApplicationRunner.sigInt)
     98
     99        exitInt = self.getExitStatuses()[0]
     100        self.assertEquals((signal.SIGINT, 0), mockSignal.signals[-1])
     101        self.assertEquals(('kill', os.getpid(), exitInt),
     102                          mockOS.actions[0])
     103
     104    def test_exitStatusAfterKillWithSIGTERM(self):
     105        """
     106        Assert appropriate exit status after sending SIGTERM.
     107        """
     108        mockOS, mockSignal = self.runTwistdInFakeEnviroment(
     109            twistd.TwistdApplicationRunner.sigTerm)
     110
     111        exitTerm = self.getExitStatuses()[2]
     112        self.assertEquals((signal.SIGTERM, 0), mockSignal.signals[-1])
     113        self.assertEquals(('kill', os.getpid(), exitTerm),
     114                          mockOS.actions[0])
     115
     116    def test_exitStatusAfterKillWithSIGBREAK(self):
     117        """
     118        Assert appropriate exit status after sending SIGBREAK.
     119        """
     120        mockOS, mockSignal = self.runTwistdInFakeEnviroment(
     121            twistd.TwistdApplicationRunner.sigBreak)
     122
     123        exitBreak = self.getExitStatuses()[1]
     124        self.assertEquals((signal.SIGBREAK, 0), mockSignal.signals[-1])
     125        self.assertEquals(('kill', os.getpid(), exitBreak),
     126                          mockOS.actions[0])
     127
     128    if not runtime.platform.isWindows():
     129        test_overwriteSIGBREAKHandler.skip = "SIGBREAK only on Windows."
     130        test_exitStatusAfterKillWithSIGBREAK.skip = "SIGBREAK only on Windows."
  • twisted/test/test_process.py

     
    12071207    """
    12081208    Neuter L{signal.signal}, but pass other attributes unscathed
    12091209    """
     1210    def __init__(self):
     1211        self.signals = []
     1212
    12101213    def signal(self, sig, action):
     1214        self.signals.append((sig, action))
    12111215        return signal.getsignal(sig)
    12121216
    12131217    def __getattr__(self, attr):
  • twisted/topfiles/761.bugfix

     
     1Twistd returns SIGINT for SIGINT signal and SIGTERM for SIGTERM signal.
     2 No newline at end of file
  • twisted/internet/base.py

     
    1111import socket # needed only for sync-dns
    1212from zope.interface import implementer, classImplements
    1313
    14 import sys
     14import sys, signal
    1515import warnings
    1616from heapq import heappush, heappop, heapify
    1717
     
    601601        """
    602602        log.msg("Received SIGINT, shutting down.")
    603603        self.callFromThread(self.stop)
     604        self._exitStatus = signal.SIGINT
    604605
    605606    def sigBreak(self, *args):
    606607        """Handle a SIGBREAK interrupt.
     
    613614        """
    614615        log.msg("Received SIGTERM, shutting down.")
    615616        self.callFromThread(self.stop)
     617        self._exitStatus = signal.SIGTERM
    616618
    617619    def disconnectAll(self):
    618620        """Disconnect every reader, and writer in the system.
     
    11501152        if signal.getsignal(signal.SIGINT) == signal.default_int_handler:
    11511153            # only handle if there isn't already a handler, e.g. for Pdb.
    11521154            signal.signal(signal.SIGINT, self.sigInt)
    1153         signal.signal(signal.SIGTERM, self.sigTerm)
     1155            signal.signal(signal.SIGTERM, self.sigTerm)
     1156            # Catch Ctrl-Break in windows
     1157            if hasattr(signal, "SIGBREAK"):
     1158                signal.signal(signal.SIGBREAK, self.sigBreak)
    11541159
    1155         # Catch Ctrl-Break in windows
    1156         if hasattr(signal, "SIGBREAK"):
    1157             signal.signal(signal.SIGBREAK, self.sigBreak)
    11581160
    1159 
    11601161    def startRunning(self, installSignalHandlers=True):
    11611162        """
    11621163        Extend the base implementation in order to remember whether signal
  • twisted/internet/test/test_base.py

     
    1919from twisted.internet.base import ThreadedResolver, DelayedCall
    2020from twisted.internet.task import Clock
    2121from twisted.trial.unittest import TestCase
     22from twisted.internet.base import _SignalReactorMixin, ReactorBase
    2223
    2324
    2425@implementer(IReactorTime, IReactorThreads)