greenio: fix fd double close; Thanks to Antonio Cuni

https://github.com/eventlet/eventlet/issues/219
This commit is contained in:
Sergey Shepelev
2015-03-31 00:58:39 +03:00
parent b2e5146dec
commit 2b6a7cef37
4 changed files with 45 additions and 22 deletions

View File

@@ -143,7 +143,9 @@ class _SocketDuckForFd(object):
raise raise
def _mark_as_closed(self): def _mark_as_closed(self):
current = self._closed
self._closed = True self._closed = True
return current
@property @property
def _sock(self): def _sock(self):
@@ -201,8 +203,10 @@ class _SocketDuckForFd(object):
self._close() self._close()
def _close(self): def _close(self):
was_closed = self._mark_as_closed()
if was_closed:
return
notify_close(self._fileno) notify_close(self._fileno)
self._mark_as_closed()
try: try:
os.close(self._fileno) os.close(self._fileno)
except: except:

View File

@@ -127,8 +127,8 @@ class GreenFileIO(_OriginalIOBase):
def close(self): def close(self):
if not self._closed: if not self._closed:
_original_os.close(self._fileno)
self._closed = True self._closed = True
_original_os.close(self._fileno)
notify_close(self._fileno) notify_close(self._fileno)
for method in [ for method in [
'fileno', 'flush', 'isatty', 'next', 'read', 'readinto', 'fileno', 'flush', 'isatty', 'next', 'read', 'readinto',

View File

@@ -15,10 +15,7 @@ from eventlet import event, greenio, debug
from eventlet.hubs import get_hub from eventlet.hubs import get_hub
from eventlet.green import select, socket, time, ssl from eventlet.green import select, socket, time, ssl
from eventlet.support import capture_stderr, get_errno, six from eventlet.support import capture_stderr, get_errno, six
from tests import ( import tests
LimitedTestCase, main,
skip_with_pyevent, skipped, skip_if, skip_on_windows,
)
if six.PY3: if six.PY3:
@@ -58,7 +55,7 @@ def using_kqueue_hub(_f):
return False return False
class TestGreenSocket(LimitedTestCase): class TestGreenSocket(tests.LimitedTestCase):
def assertWriteToClosedFileRaises(self, fd): def assertWriteToClosedFileRaises(self, fd):
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
# 2.x socket._fileobjects are odd: writes don't check # 2.x socket._fileobjects are odd: writes don't check
@@ -481,7 +478,7 @@ class TestGreenSocket(LimitedTestCase):
server.close() server.close()
client.close() client.close()
@skip_with_pyevent @tests.skip_with_pyevent
def test_raised_multiple_readers(self): def test_raised_multiple_readers(self):
debug.hub_prevent_multiple_readers(True) debug.hub_prevent_multiple_readers(True)
@@ -503,9 +500,9 @@ class TestGreenSocket(LimitedTestCase):
s.sendall(b'b') s.sendall(b'b')
a.wait() a.wait()
@skip_with_pyevent @tests.skip_with_pyevent
@skip_if(using_epoll_hub) @tests.skip_if(using_epoll_hub)
@skip_if(using_kqueue_hub) @tests.skip_if(using_kqueue_hub)
def test_closure(self): def test_closure(self):
def spam_to_me(address): def spam_to_me(address):
sock = eventlet.connect(address) sock = eventlet.connect(address)
@@ -655,8 +652,8 @@ def test_get_fileno_of_a_socket_with_fileno_returning_wrong_type_fails():
assert False, 'Expected TypeError not raised' assert False, 'Expected TypeError not raised'
class TestGreenPipe(LimitedTestCase): class TestGreenPipe(tests.LimitedTestCase):
@skip_on_windows @tests.skip_on_windows
def setUp(self): def setUp(self):
super(self.__class__, self).setUp() super(self.__class__, self).setUp()
self.tempdir = tempfile.mkdtemp('_green_pipe_test') self.tempdir = tempfile.mkdtemp('_green_pipe_test')
@@ -768,10 +765,10 @@ class TestGreenPipe(LimitedTestCase):
self.assertEqual(f.tell(), 9) self.assertEqual(f.tell(), 9)
class TestGreenIoLong(LimitedTestCase): class TestGreenIoLong(tests.LimitedTestCase):
TEST_TIMEOUT = 10 # the test here might take a while depending on the OS TEST_TIMEOUT = 10 # the test here might take a while depending on the OS
@skip_with_pyevent @tests.skip_with_pyevent
def test_multiple_readers(self, clibufsize=False): def test_multiple_readers(self, clibufsize=False):
debug.hub_prevent_multiple_readers(False) debug.hub_prevent_multiple_readers(False)
recvsize = 2 * min_buf_size() recvsize = 2 * min_buf_size()
@@ -824,21 +821,21 @@ class TestGreenIoLong(LimitedTestCase):
assert len(results2) > 0 assert len(results2) > 0
debug.hub_prevent_multiple_readers() debug.hub_prevent_multiple_readers()
@skipped # by rdw because it fails but it's not clear how to make it pass @tests.skipped # by rdw because it fails but it's not clear how to make it pass
@skip_with_pyevent @tests.skip_with_pyevent
def test_multiple_readers2(self): def test_multiple_readers2(self):
self.test_multiple_readers(clibufsize=True) self.test_multiple_readers(clibufsize=True)
class TestGreenIoStarvation(LimitedTestCase): class TestGreenIoStarvation(tests.LimitedTestCase):
# fixme: this doesn't succeed, because of eventlet's predetermined # fixme: this doesn't succeed, because of eventlet's predetermined
# ordering. two processes, one with server, one with client eventlets # ordering. two processes, one with server, one with client eventlets
# might be more reliable? # might be more reliable?
TEST_TIMEOUT = 300 # the test here might take a while depending on the OS TEST_TIMEOUT = 300 # the test here might take a while depending on the OS
@skipped # by rdw, because it fails but it's not clear how to make it pass @tests.skipped # by rdw, because it fails but it's not clear how to make it pass
@skip_with_pyevent @tests.skip_with_pyevent
def test_server_starvation(self, sendloops=15): def test_server_starvation(self, sendloops=15):
recvsize = 2 * min_buf_size() recvsize = 2 * min_buf_size()
sendsize = 10000 * recvsize sendsize = 10000 * recvsize
@@ -955,5 +952,5 @@ def test_socket_del_fails_gracefully_when_not_fully_initialized():
assert err.getvalue() == '' assert err.getvalue() == ''
if __name__ == '__main__': def test_double_close_219():
main() tests.run_isolated('greenio_double_close_219.py')

View File

@@ -0,0 +1,22 @@
__test__ = False
def main():
import eventlet
eventlet.monkey_patch()
import subprocess
import gc
p = subprocess.Popen(['ls'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# the following line creates a _SocketDuckForFd(3) and .close()s it, but the
# object has not been collected by the GC yet
p.communicate()
f = open('/dev/null', 'rb') # f.fileno() == 3
gc.collect() # this calls the __del__ of _SocketDuckForFd(3), close()ing it again
f.close() # OSError, because the fd 3 has already been closed
print('pass')
if __name__ == '__main__':
main()