1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Whitebox tests for TCP APIs.
8 import errno, socket, os
15 from twisted.trial.unittest import TestCase
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
25 class PlatformAssumptionsTestCase(TestCase):
27 Test assumptions about platform behaviors.
33 if resource is not None:
34 # On some buggy platforms we might leak FDs, and the test will
35 # fail creating the initial two sockets we *do* want to
36 # succeed. So, we make the soft limit the current number of fds
37 # plus two more (for the two sockets we want to succeed). If we've
38 # leaked too many fds for that to work, there's nothing we can
40 from twisted.internet.process import _listOpenFDs
41 newLimit = len(_listOpenFDs()) + 2
42 self.originalFileLimit = resource.getrlimit(resource.RLIMIT_NOFILE)
43 resource.setrlimit(resource.RLIMIT_NOFILE, (newLimit, self.originalFileLimit[1]))
44 self.socketLimit = newLimit + 100
48 while self.openSockets:
49 self.openSockets.pop().close()
50 if resource is not None:
51 # OS X implicitly lowers the hard limit in the setrlimit call
52 # above. Retrieve the new hard limit to pass in to this
53 # setrlimit call, so that it doesn't give us a permission denied
55 currentHardLimit = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
56 newSoftLimit = min(self.originalFileLimit[0], currentHardLimit)
57 resource.setrlimit(resource.RLIMIT_NOFILE, (newSoftLimit, currentHardLimit))
62 Create and return a new socket object, also tracking it so it can be
63 closed in the test tear down.
66 self.openSockets.append(s)
70 def test_acceptOutOfFiles(self):
72 Test that the platform accept(2) call fails with either L{EMFILE} or
73 L{ENOBUFS} when there are too many file descriptors open.
75 # Make a server to which to connect
77 port.bind(('127.0.0.1', 0))
78 serverPortNumber = port.getsockname()[1]
81 # Make a client to use to connect to the server
82 client = self.socket()
83 client.setblocking(False)
85 # Use up all the rest of the file descriptors.
86 for i in xrange(self.socketLimit):
89 except socket.error, e:
90 if e.args[0] in (EMFILE, ENOBUFS):
91 # The desired state has been achieved.
94 # Some unexpected error occurred.
97 self.fail("Could provoke neither EMFILE nor ENOBUFS from platform.")
99 # Non-blocking connect is supposed to fail, but this is not true
100 # everywhere (e.g. freeBSD)
101 self.assertIn(client.connect_ex(('127.0.0.1', serverPortNumber)),
104 # Make sure that the accept call fails in the way we expect.
105 exc = self.assertRaises(socket.error, port.accept)
106 self.assertIn(exc.args[0], (EMFILE, ENOBUFS))
107 if platform.getType() == "win32":
108 test_acceptOutOfFiles.skip = (
109 "Windows requires an unacceptably large amount of resources to "
110 "provoke this behavior in the naive manner.")
114 class SelectReactorTestCase(TestCase):
116 Tests for select-specific failure conditions.
122 log.addObserver(self.messages.append)
126 log.removeObserver(self.messages.append)
127 return gatherResults([
128 maybeDeferred(p.stopListening)
129 for p in self.ports])
132 def port(self, portNumber, factory, interface):
134 Create, start, and return a new L{Port}, also tracking it so it can
135 be stopped in the test tear down.
137 p = Port(portNumber, factory, interface=interface)
143 def _acceptFailureTest(self, socketErrorNumber):
145 Test behavior in the face of an exception from C{accept(2)}.
147 On any exception which indicates the platform is unable or unwilling
148 to allocate further resources to us, the existing port should remain
149 listening, a message should be logged, and the exception should not
150 propagate outward from doRead.
152 @param socketErrorNumber: The errno to simulate from accept.
154 class FakeSocket(object):
156 Pretend to be a socket in an overloaded system.
160 socketErrorNumber, os.strerror(socketErrorNumber))
162 factory = ServerFactory()
163 port = self.port(0, factory, interface='127.0.0.1')
164 originalSocket = port.socket
166 port.socket = FakeSocket()
170 expectedFormat = "Could not accept new connection (%s)"
171 expectedErrorCode = errno.errorcode[socketErrorNumber]
172 expectedMessage = expectedFormat % (expectedErrorCode,)
173 for msg in self.messages:
174 if msg.get('message') == (expectedMessage,):
177 self.fail("Log event for failed accept not found in "
178 "%r" % (self.messages,))
180 port.socket = originalSocket
183 def test_tooManyFilesFromAccept(self):
185 C{accept(2)} can fail with C{EMFILE} when there are too many open file
186 descriptors in the process. Test that this doesn't negatively impact
187 any other existing connections.
189 C{EMFILE} mainly occurs on Linux when the open file rlimit is
192 return self._acceptFailureTest(EMFILE)
195 def test_noBufferSpaceFromAccept(self):
197 Similar to L{test_tooManyFilesFromAccept}, but test the case where
198 C{accept(2)} fails with C{ENOBUFS}.
200 This mainly occurs on Windows and FreeBSD, but may be possible on
201 Linux and other platforms as well.
203 return self._acceptFailureTest(ENOBUFS)
206 def test_connectionAbortedFromAccept(self):
208 Similar to L{test_tooManyFilesFromAccept}, but test the case where
209 C{accept(2)} fails with C{ECONNABORTED}.
211 It is not clear whether this is actually possible for TCP
212 connections on modern versions of Linux.
214 return self._acceptFailureTest(ECONNABORTED)
217 def test_noFilesFromAccept(self):
219 Similar to L{test_tooManyFilesFromAccept}, but test the case where
220 C{accept(2)} fails with C{ENFILE}.
222 This can occur on Linux when the system has exhausted (!) its supply
225 return self._acceptFailureTest(ENFILE)
226 if platform.getType() == 'win32':
227 test_noFilesFromAccept.skip = "Windows accept(2) cannot generate ENFILE"
230 def test_noMemoryFromAccept(self):
232 Similar to L{test_tooManyFilesFromAccept}, but test the case where
233 C{accept(2)} fails with C{ENOMEM}.
235 On Linux at least, this can sensibly occur, even in a Python program
236 (which eats memory like no ones business), when memory has become
237 fragmented or low memory has been filled (d_alloc calls
238 kmem_cache_alloc calls kmalloc - kmalloc only allocates out of low
241 return self._acceptFailureTest(ENOMEM)
242 if platform.getType() == 'win32':
243 test_noMemoryFromAccept.skip = "Windows accept(2) cannot generate ENOMEM"
245 if not interfaces.IReactorFDSet.providedBy(reactor):
246 skipMsg = 'This test only applies to reactors that implement IReactorFDset'
247 PlatformAssumptionsTestCase.skip = skipMsg
248 SelectReactorTestCase.skip = skipMsg