| 1 | Index: twisted/internet/test/test_process.py |
|---|
| 2 | =================================================================== |
|---|
| 3 | --- twisted/internet/test/test_process.py (revision 30737) |
|---|
| 4 | +++ twisted/internet/test/test_process.py (working copy) |
|---|
| 5 | @@ -20,9 +20,9 @@ |
|---|
| 6 | from twisted.internet.defer import Deferred, succeed |
|---|
| 7 | from twisted.internet.protocol import ProcessProtocol |
|---|
| 8 | from twisted.internet.error import ProcessDone, ProcessTerminated |
|---|
| 9 | +from twisted.python import runtime |
|---|
| 10 | +from twisted.internet import process |
|---|
| 11 | |
|---|
| 12 | - |
|---|
| 13 | - |
|---|
| 14 | class _ShutdownCallbackProcessProtocol(ProcessProtocol): |
|---|
| 15 | """ |
|---|
| 16 | An L{IProcessProtocol} which fires a Deferred when the process it is |
|---|
| 17 | @@ -590,3 +590,135 @@ |
|---|
| 18 | "Twisted 10.0.0: There is no longer any potential for zombie " |
|---|
| 19 | "process.") |
|---|
| 20 | self.assertEquals(len(warnings), 1) |
|---|
| 21 | + |
|---|
| 22 | + |
|---|
| 23 | +class FakeResourceModule(object): |
|---|
| 24 | + RLIMIT_NOFILE = 1 |
|---|
| 25 | + def getrlimit(self, no): |
|---|
| 26 | + return [0, 9223372036854775808] |
|---|
| 27 | + # TODO: Test that this gets rounded down |
|---|
| 28 | + |
|---|
| 29 | +class FDDetectorTest(TestCase): |
|---|
| 30 | + """ |
|---|
| 31 | + Tests for _FDDetector class in twisted.internet.process, which detects |
|---|
| 32 | + which function to drop in place for the _listOpenFDs method. |
|---|
| 33 | + """ |
|---|
| 34 | + skip = runtime.platform.getType() == 'win32' |
|---|
| 35 | + sane = False |
|---|
| 36 | + procfs = False |
|---|
| 37 | + devfs = False |
|---|
| 38 | + opened_file = False |
|---|
| 39 | + saved_resource_module = None |
|---|
| 40 | + |
|---|
| 41 | + def getpid(self): |
|---|
| 42 | + return 123 |
|---|
| 43 | + |
|---|
| 44 | + def listdir(self, arg): |
|---|
| 45 | + if arg == '/proc/123/fd': |
|---|
| 46 | + if self.procfs: |
|---|
| 47 | + return ["0","1","2"] |
|---|
| 48 | + else: |
|---|
| 49 | + raise OSError |
|---|
| 50 | + |
|---|
| 51 | + if arg == '/dev/fd': |
|---|
| 52 | + if self.devfs: |
|---|
| 53 | + if not self.sane: |
|---|
| 54 | + # Always return the same thing |
|---|
| 55 | + return ["0","1","2"] |
|---|
| 56 | + else: |
|---|
| 57 | + if self.opened_file: |
|---|
| 58 | + return ["0","1","2","3"] |
|---|
| 59 | + else: |
|---|
| 60 | + return ["0","1","2"] |
|---|
| 61 | + else: |
|---|
| 62 | + raise OSError |
|---|
| 63 | + |
|---|
| 64 | + def openfile(self, fname, mode): |
|---|
| 65 | + self.opened_file = True |
|---|
| 66 | + |
|---|
| 67 | + def save_resource_module(self): |
|---|
| 68 | + try: |
|---|
| 69 | + import resource |
|---|
| 70 | + self.saved_resource_module = resource |
|---|
| 71 | + except: |
|---|
| 72 | + self.saved_resource_module = None |
|---|
| 73 | + |
|---|
| 74 | + def hide_resource_module(self): |
|---|
| 75 | + import sys |
|---|
| 76 | + sys.modules['resource'] = None |
|---|
| 77 | + |
|---|
| 78 | + def reveal_resource_module(self): |
|---|
| 79 | + import sys |
|---|
| 80 | + sys.modules['resource'] = FakeResourceModule() |
|---|
| 81 | + |
|---|
| 82 | + def replace_resource_module(self): |
|---|
| 83 | + sys.modules['resource'] = self.saved_resource_module |
|---|
| 84 | + |
|---|
| 85 | + def setUp(self): |
|---|
| 86 | + self.detector = process._FDDetector() |
|---|
| 87 | + self.detector.listdir = self.listdir |
|---|
| 88 | + self.detector.getpid = self.getpid |
|---|
| 89 | + self.detector.openfile = self.openfile |
|---|
| 90 | + |
|---|
| 91 | + def test_fddetector_sane_devfd(self): |
|---|
| 92 | + """ |
|---|
| 93 | + e.g., FreeBSD with fdescfs mounted |
|---|
| 94 | + """ |
|---|
| 95 | + self.procfs = False |
|---|
| 96 | + self.devfs = True |
|---|
| 97 | + self.sane = True |
|---|
| 98 | + self.assertIdentical( |
|---|
| 99 | + self.detector._getImplementation(), |
|---|
| 100 | + process._devFDImplementation |
|---|
| 101 | + ) |
|---|
| 102 | + |
|---|
| 103 | + def test_fddetector_insane_devfd(self): |
|---|
| 104 | + """ |
|---|
| 105 | + e.g., FreeBSD without fdescfs mounted |
|---|
| 106 | + """ |
|---|
| 107 | + self.procfs = False |
|---|
| 108 | + self.devfs = True |
|---|
| 109 | + self.sane = False |
|---|
| 110 | + self.assertIdentical( |
|---|
| 111 | + self.detector._getImplementation(), |
|---|
| 112 | + process._fallbackFDImplementation |
|---|
| 113 | + ) |
|---|
| 114 | + |
|---|
| 115 | + def test_fddetector_procfd(self): |
|---|
| 116 | + """ |
|---|
| 117 | + e.g., Linux |
|---|
| 118 | + """ |
|---|
| 119 | + self.devfs = False |
|---|
| 120 | + self.procfs = True |
|---|
| 121 | + self.assertIdentical( |
|---|
| 122 | + self.detector._getImplementation(), |
|---|
| 123 | + process._procFDImplementation |
|---|
| 124 | + ) |
|---|
| 125 | + |
|---|
| 126 | + def test_fddetector_resource_importable(self): |
|---|
| 127 | + """ |
|---|
| 128 | + e.g., IRIX? |
|---|
| 129 | + """ |
|---|
| 130 | + self.devfs = False |
|---|
| 131 | + self.procfs = False |
|---|
| 132 | + self.resource_importable = True |
|---|
| 133 | + self.reveal_resource_module() |
|---|
| 134 | + self.assertIdentical( |
|---|
| 135 | + self.detector._getImplementation(), |
|---|
| 136 | + process._resourceFDImplementation |
|---|
| 137 | + ) |
|---|
| 138 | + self.replace_resource_module() |
|---|
| 139 | + |
|---|
| 140 | + def test_fddetector_resource_unimportable(self): |
|---|
| 141 | + """ |
|---|
| 142 | + e.g., who knows |
|---|
| 143 | + """ |
|---|
| 144 | + self.devfs = False |
|---|
| 145 | + self.procfs = False |
|---|
| 146 | + self.hide_resource_module() |
|---|
| 147 | + self.assertIdentical( |
|---|
| 148 | + self.detector._getImplementation(), |
|---|
| 149 | + process._fallbackFDImplementation |
|---|
| 150 | + ) |
|---|
| 151 | + self.replace_resource_module() |
|---|
| 152 | + |
|---|
| 153 | Index: twisted/internet/process.py |
|---|
| 154 | =================================================================== |
|---|
| 155 | --- twisted/internet/process.py (revision 30737) |
|---|
| 156 | +++ twisted/internet/process.py (working copy) |
|---|
| 157 | @@ -466,35 +466,74 @@ |
|---|
| 158 | return "<%s pid=%s status=%s>" % (self.__class__.__name__, |
|---|
| 159 | self.pid, self.status) |
|---|
| 160 | |
|---|
| 161 | +def _listOpenFDs(): |
|---|
| 162 | + return _setFDImplementation() |
|---|
| 163 | |
|---|
| 164 | +def _setFDImplementation(): |
|---|
| 165 | + detector = _FDDetector() |
|---|
| 166 | + _listOpenFDs = detector._getImplementation() |
|---|
| 167 | + return _listOpenFDs() |
|---|
| 168 | |
|---|
| 169 | -def _listOpenFDs(): |
|---|
| 170 | - """ |
|---|
| 171 | - Return an iterable of potentially open file descriptors. |
|---|
| 172 | +def _devFDImplementation(): |
|---|
| 173 | + dname = "/dev/fd" |
|---|
| 174 | + result = [int(fd) for fd in os.listdir(dname)] |
|---|
| 175 | + return result |
|---|
| 176 | |
|---|
| 177 | - This function returns an iterable over the contents of /dev/fd or |
|---|
| 178 | - /proc/<pid>/fd, if they're available on the platform. If they're not, the |
|---|
| 179 | - returned value is the range [0, maxfds], where 'maxfds' is at least 256. |
|---|
| 180 | - """ |
|---|
| 181 | - dname = "/dev/fd" |
|---|
| 182 | - try: |
|---|
| 183 | - return [int(fd) for fd in os.listdir(dname)] |
|---|
| 184 | - except: |
|---|
| 185 | - dname = "/proc/%d/fd" % (os.getpid(),) |
|---|
| 186 | +def _procFDImplementation(): |
|---|
| 187 | + dname = "/proc/%d/fd" % (os.getpid(),) |
|---|
| 188 | + return [int(fd) for fd in os.listdir(dname)] |
|---|
| 189 | + |
|---|
| 190 | +def _resourceFDImplementation(): |
|---|
| 191 | + import resource |
|---|
| 192 | + maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + 1 |
|---|
| 193 | + # OS-X reports 9223372036854775808. That's a lot of fds |
|---|
| 194 | + # to close |
|---|
| 195 | + if maxfds > 1024: |
|---|
| 196 | + maxfds = 1024 |
|---|
| 197 | + return xrange(maxfds) |
|---|
| 198 | + |
|---|
| 199 | +def _fallbackFDImplementation(): |
|---|
| 200 | + maxfds = 256 |
|---|
| 201 | + return xrange(maxfds) |
|---|
| 202 | + |
|---|
| 203 | +class _FDDetector(object): |
|---|
| 204 | + # So that we can unit test this |
|---|
| 205 | + listdir = os.listdir |
|---|
| 206 | + getpid = os.getpid |
|---|
| 207 | + openfile = open |
|---|
| 208 | + |
|---|
| 209 | + def _getImplementation(self): |
|---|
| 210 | + # Check if /dev/fd works, if so, use that |
|---|
| 211 | + # Otherwise, check if /proc/%d/fd exists, if so use that |
|---|
| 212 | + # Otherwise, ask resource.getrlimit, if that throws an exception, then |
|---|
| 213 | + # fallback to _fallbackFDImplementation |
|---|
| 214 | try: |
|---|
| 215 | - return [int(fd) for fd in os.listdir(dname)] |
|---|
| 216 | + self.listdir("/dev/fd") |
|---|
| 217 | + if self._checkDevFDSanity(): # FreeBSD support :-) |
|---|
| 218 | + return _devFDImplementation |
|---|
| 219 | + else: |
|---|
| 220 | + return _fallbackFDImplementation |
|---|
| 221 | + |
|---|
| 222 | except: |
|---|
| 223 | try: |
|---|
| 224 | - import resource |
|---|
| 225 | - maxfds = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + 1 |
|---|
| 226 | - # OS-X reports 9223372036854775808. That's a lot of fds |
|---|
| 227 | - # to close |
|---|
| 228 | - if maxfds > 1024: |
|---|
| 229 | - maxfds = 1024 |
|---|
| 230 | + self.listdir("/proc/%d/fd" % (self.getpid(),)) |
|---|
| 231 | + return _procFDImplementation |
|---|
| 232 | except: |
|---|
| 233 | - maxfds = 256 |
|---|
| 234 | - return xrange(maxfds) |
|---|
| 235 | + try: |
|---|
| 236 | + _resourceFDImplementation() # Imports resource |
|---|
| 237 | + return _resourceFDImplementation |
|---|
| 238 | + except: |
|---|
| 239 | + return _fallbackFDImplementation |
|---|
| 240 | |
|---|
| 241 | + def _checkDevFDSanity(self): |
|---|
| 242 | + """ |
|---|
| 243 | + Returns true iff opening a file modifies the fds visible |
|---|
| 244 | + in /dev/fd, as it should on a sane platform. |
|---|
| 245 | + """ |
|---|
| 246 | + start = self.listdir("/dev/fd") |
|---|
| 247 | + fp = self.openfile("/dev/null", "r") |
|---|
| 248 | + end = self.listdir("/dev/fd") |
|---|
| 249 | + return start != end |
|---|
| 250 | |
|---|
| 251 | |
|---|
| 252 | class Process(_BaseProcess): |
|---|