| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2006 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 Whitebox tests for TCP APIs. | |
| 6 """ | |
| 7 | |
| 8 import errno, socket, os | |
| 9 | |
| 10 try: | |
| 11 import resource | |
| 12 except ImportError: | |
| 13 resource = None | |
| 14 | |
| 15 from twisted.trial.unittest import TestCase | |
| 16 | |
| 17 from twisted.python import log | |
| 18 from twisted.internet.tcp import ECONNABORTED, ENOMEM, ENFILE, EMFILE, ENOBUFS,
EINPROGRESS, Port | |
| 19 from twisted.internet.protocol import ServerFactory | |
| 20 from twisted.python.runtime import platform | |
| 21 from twisted.internet.defer import maybeDeferred, gatherResults | |
| 22 from twisted.internet import reactor, interfaces | |
| 23 | |
| 24 | |
| 25 class PlatformAssumptionsTestCase(TestCase): | |
| 26 """ | |
| 27 Test assumptions about platform behaviors. | |
| 28 """ | |
| 29 socketLimit = 8192 | |
| 30 | |
| 31 def setUp(self): | |
| 32 self.openSockets = [] | |
| 33 if resource is not None: | |
| 34 self.originalFileLimit = resource.getrlimit(resource.RLIMIT_NOFILE) | |
| 35 resource.setrlimit(resource.RLIMIT_NOFILE, (128, self.originalFileLi
mit[1])) | |
| 36 self.socketLimit = 256 | |
| 37 | |
| 38 | |
| 39 def tearDown(self): | |
| 40 while self.openSockets: | |
| 41 self.openSockets.pop().close() | |
| 42 if resource is not None: | |
| 43 # OS X implicitly lowers the hard limit in the setrlimit call | |
| 44 # above. Retrieve the new hard limit to pass in to this | |
| 45 # setrlimit call, so that it doesn't give us a permission denied | |
| 46 # error. | |
| 47 currentHardLimit = resource.getrlimit(resource.RLIMIT_NOFILE)[1] | |
| 48 newSoftLimit = min(self.originalFileLimit[0], currentHardLimit) | |
| 49 resource.setrlimit(resource.RLIMIT_NOFILE, (newSoftLimit, currentHar
dLimit)) | |
| 50 | |
| 51 | |
| 52 def socket(self): | |
| 53 """ | |
| 54 Create and return a new socket object, also tracking it so it can be | |
| 55 closed in the test tear down. | |
| 56 """ | |
| 57 s = socket.socket() | |
| 58 self.openSockets.append(s) | |
| 59 return s | |
| 60 | |
| 61 | |
| 62 def test_acceptOutOfFiles(self): | |
| 63 """ | |
| 64 Test that the platform accept(2) call fails with either L{EMFILE} or | |
| 65 L{ENOBUFS} when there are too many file descriptors open. | |
| 66 """ | |
| 67 # Make a server to which to connect | |
| 68 port = self.socket() | |
| 69 port.bind(('127.0.0.1', 0)) | |
| 70 serverPortNumber = port.getsockname()[1] | |
| 71 port.listen(5) | |
| 72 | |
| 73 # Use up all the file descriptors | |
| 74 for i in xrange(self.socketLimit): | |
| 75 try: | |
| 76 self.socket() | |
| 77 except socket.error, e: | |
| 78 if e.args[0] in (EMFILE, ENOBUFS): | |
| 79 self.openSockets.pop().close() | |
| 80 break | |
| 81 else: | |
| 82 raise | |
| 83 else: | |
| 84 self.fail("Could provoke neither EMFILE nor ENOBUFS from platform.") | |
| 85 | |
| 86 # Make a client to use to connect to the server | |
| 87 client = self.socket() | |
| 88 client.setblocking(False) | |
| 89 | |
| 90 # Non-blocking connect is supposed to fail, but this is not true | |
| 91 # everywhere (e.g. freeBSD) | |
| 92 self.assertIn(client.connect_ex(('127.0.0.1', serverPortNumber)), | |
| 93 (0, EINPROGRESS)) | |
| 94 | |
| 95 # Make sure that the accept call fails in the way we expect. | |
| 96 exc = self.assertRaises(socket.error, port.accept) | |
| 97 self.assertIn(exc.args[0], (EMFILE, ENOBUFS)) | |
| 98 if platform.getType() == "win32": | |
| 99 test_acceptOutOfFiles.skip = ( | |
| 100 "Windows requires an unacceptably large amount of resources to " | |
| 101 "provoke this behavior in the naive manner.") | |
| 102 | |
| 103 | |
| 104 | |
| 105 class SelectReactorTestCase(TestCase): | |
| 106 """ | |
| 107 Tests for select-specific failure conditions. | |
| 108 """ | |
| 109 | |
| 110 def setUp(self): | |
| 111 self.ports = [] | |
| 112 self.messages = [] | |
| 113 log.addObserver(self.messages.append) | |
| 114 | |
| 115 | |
| 116 def tearDown(self): | |
| 117 log.removeObserver(self.messages.append) | |
| 118 return gatherResults([ | |
| 119 maybeDeferred(p.stopListening) | |
| 120 for p in self.ports]) | |
| 121 | |
| 122 | |
| 123 def port(self, portNumber, factory, interface): | |
| 124 """ | |
| 125 Create, start, and return a new L{Port}, also tracking it so it can | |
| 126 be stopped in the test tear down. | |
| 127 """ | |
| 128 p = Port(portNumber, factory, interface=interface) | |
| 129 p.startListening() | |
| 130 self.ports.append(p) | |
| 131 return p | |
| 132 | |
| 133 | |
| 134 def _acceptFailureTest(self, socketErrorNumber): | |
| 135 """ | |
| 136 Test behavior in the face of an exception from C{accept(2)}. | |
| 137 | |
| 138 On any exception which indicates the platform is unable or unwilling | |
| 139 to allocate further resources to us, the existing port should remain | |
| 140 listening, a message should be logged, and the exception should not | |
| 141 propagate outward from doRead. | |
| 142 | |
| 143 @param socketErrorNumber: The errno to simulate from accept. | |
| 144 """ | |
| 145 class FakeSocket(object): | |
| 146 """ | |
| 147 Pretend to be a socket in an overloaded system. | |
| 148 """ | |
| 149 def accept(self): | |
| 150 raise socket.error( | |
| 151 socketErrorNumber, os.strerror(socketErrorNumber)) | |
| 152 | |
| 153 factory = ServerFactory() | |
| 154 port = self.port(0, factory, interface='127.0.0.1') | |
| 155 originalSocket = port.socket | |
| 156 try: | |
| 157 port.socket = FakeSocket() | |
| 158 | |
| 159 port.doRead() | |
| 160 | |
| 161 expectedFormat = "Could not accept new connection (%s)" | |
| 162 expectedErrorCode = errno.errorcode[socketErrorNumber] | |
| 163 expectedMessage = expectedFormat % (expectedErrorCode,) | |
| 164 for msg in self.messages: | |
| 165 if msg.get('message') == (expectedMessage,): | |
| 166 break | |
| 167 else: | |
| 168 self.fail("Log event for failed accept not found in " | |
| 169 "%r" % (self.messages,)) | |
| 170 finally: | |
| 171 port.socket = originalSocket | |
| 172 | |
| 173 | |
| 174 def test_tooManyFilesFromAccept(self): | |
| 175 """ | |
| 176 C{accept(2)} can fail with C{EMFILE} when there are too many open file | |
| 177 descriptors in the process. Test that this doesn't negatively impact | |
| 178 any other existing connections. | |
| 179 | |
| 180 C{EMFILE} mainly occurs on Linux when the open file rlimit is | |
| 181 encountered. | |
| 182 """ | |
| 183 return self._acceptFailureTest(EMFILE) | |
| 184 | |
| 185 | |
| 186 def test_noBufferSpaceFromAccept(self): | |
| 187 """ | |
| 188 Similar to L{test_tooManyFilesFromAccept}, but test the case where | |
| 189 C{accept(2)} fails with C{ENOBUFS}. | |
| 190 | |
| 191 This mainly occurs on Windows and FreeBSD, but may be possible on | |
| 192 Linux and other platforms as well. | |
| 193 """ | |
| 194 return self._acceptFailureTest(ENOBUFS) | |
| 195 | |
| 196 | |
| 197 def test_connectionAbortedFromAccept(self): | |
| 198 """ | |
| 199 Similar to L{test_tooManyFilesFromAccept}, but test the case where | |
| 200 C{accept(2)} fails with C{ECONNABORTED}. | |
| 201 | |
| 202 It is not clear whether this is actually possible for TCP | |
| 203 connections on modern versions of Linux. | |
| 204 """ | |
| 205 return self._acceptFailureTest(ECONNABORTED) | |
| 206 | |
| 207 | |
| 208 def test_noFilesFromAccept(self): | |
| 209 """ | |
| 210 Similar to L{test_tooManyFilesFromAccept}, but test the case where | |
| 211 C{accept(2)} fails with C{ENFILE}. | |
| 212 | |
| 213 This can occur on Linux when the system has exhausted (!) its supply | |
| 214 of inodes. | |
| 215 """ | |
| 216 return self._acceptFailureTest(ENFILE) | |
| 217 if platform.getType() == 'win32': | |
| 218 test_noFilesFromAccept.skip = "Windows accept(2) cannot generate ENFILE" | |
| 219 | |
| 220 | |
| 221 def test_noMemoryFromAccept(self): | |
| 222 """ | |
| 223 Similar to L{test_tooManyFilesFromAccept}, but test the case where | |
| 224 C{accept(2)} fails with C{ENOMEM}. | |
| 225 | |
| 226 On Linux at least, this can sensibly occur, even in a Python program | |
| 227 (which eats memory like no ones business), when memory has become | |
| 228 fragmented or low memory has been filled (d_alloc calls | |
| 229 kmem_cache_alloc calls kmalloc - kmalloc only allocates out of low | |
| 230 memory). | |
| 231 """ | |
| 232 return self._acceptFailureTest(ENOMEM) | |
| 233 if platform.getType() == 'win32': | |
| 234 test_noMemoryFromAccept.skip = "Windows accept(2) cannot generate ENOMEM
" | |
| 235 | |
| 236 if not interfaces.IReactorFDSet.providedBy(reactor): | |
| 237 skipMsg = 'This test only applies to reactors that implement IReactorFDset' | |
| 238 PlatformAssumptionsTestCase.skip = skipMsg | |
| 239 SelectReactorTestCase.skip = skipMsg | |
| 240 | |
| OLD | NEW |