Skip to content

Instantly share code, notes, and snippets.

@temoto
Last active December 10, 2015 21:58
Show Gist options
  • Save temoto/4498139 to your computer and use it in GitHub Desktop.
Save temoto/4498139 to your computer and use it in GitHub Desktop.
diff --git a/eventlet/green/ssl.py b/eventlet/green/ssl.py
index 88c47a3..62ab51e 100644
--- a/eventlet/green/ssl.py
+++ b/eventlet/green/ssl.py
@@ -127,9 +140,11 @@ class GreenSSLSocket(__ssl.SSLSocket):
self.__class__)
amount = len(data)
count = 0
- while (count < amount):
+ while count < amount:
v = self.send(data[count:])
count += v
+ if v == 0:
+ trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
return amount
else:
while True:
diff --git a/tests/__init__.py b/tests/__init__.py
index 7732f8c..9306ee0 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,12 +1,15 @@
# package is named tests, not test, so it won't be confused with test in stdlib
-import sys
-import os
import errno
+import os
+import resource
+import signal
import unittest
import warnings
+import eventlet
from eventlet import debug, hubs
+
# convenience for importers
main = unittest.main
@@ -114,11 +117,13 @@ def skip_if_no_ssl(func):
""" Decorator that skips a test if SSL is not available."""
try:
import eventlet.green.ssl
+ return func
except ImportError:
try:
import eventlet.green.OpenSSL
+ return func
except ImportError:
- skipped(func)
+ return skipped(func)
class TestIsTakingTooLong(Exception):
@@ -128,25 +133,41 @@ class TestIsTakingTooLong(Exception):
class LimitedTestCase(unittest.TestCase):
""" Unittest subclass that adds a timeout to all tests. Subclasses must
- be sure to call the LimitedTestCase setUp and tearDown methods. The default
+ be sure to call the LimitedTestCase setUp and tearDown methods. The default
timeout is 1 second, change it by setting self.TEST_TIMEOUT to the desired
quantity."""
-
+
TEST_TIMEOUT = 1
+
def setUp(self):
import eventlet
- self.timer = eventlet.Timeout(self.TEST_TIMEOUT,
+ self.sig_alarm = False
+ self.timer = eventlet.Timeout(self.TEST_TIMEOUT,
TestIsTakingTooLong(self.TEST_TIMEOUT))
def reset_timeout(self, new_timeout):
"""Changes the timeout duration; only has effect during one test case"""
import eventlet
self.timer.cancel()
- self.timer = eventlet.Timeout(new_timeout,
+ self.timer = eventlet.Timeout(new_timeout,
TestIsTakingTooLong(new_timeout))
+ def set_alarm(self, new_timeout):
+ def sig_alarm_handler(sig, frame):
+ raise TestIsTakingTooLong(new_timeout)
+
+ self.sig_alarm = True
+ prev_sig_handler = signal.signal(signal.SIGALRM, sig_alarm_handler)
+ prev_alarm = signal.alarm(new_timeout)
+ self.assertEqual(prev_alarm, 0, "LimitedTestCase.set_signal_alarm() is incompatible with code that uses signal.alarm()")
+ self.assertEqual(prev_sig_handler, signal.SIG_DFL, "LimitedTestCase.set_signal_alarm() is incompatible with code that installs SIGALRM handler.")
+
def tearDown(self):
self.timer.cancel()
+ if self.sig_alarm:
+ signal.alarm(0)
+ signal.signal(signal.SIGALRM, signal.SIG_DFL)
+
try:
hub = hubs.get_hub()
num_readers = len(hub.get_readers())
@@ -173,6 +194,18 @@ class LimitedTestCase(unittest.TestCase):
assertLessThanEqual = assert_less_than_equal
+ def assertIdleCpuUsage(self, duration, allowed_part):
+ r1 = resource.getrusage(resource.RUSAGE_SELF)
+ eventlet.sleep(duration)
+ r2 = resource.getrusage(resource.RUSAGE_SELF)
+ utime = r2.ru_utime - r1.ru_utime
+ stime = r2.ru_stime - r1.ru_stime
+ self.assertTrue(utime + stime < duration * allowed_part,
+ "CPU usage over limit: user %.0f%% sys %.0f%% allowed %.0f%%" % (
+ utime / duration * 100, stime / duration * 100,
+ allowed_part * 100))
+
+
def verify_hub_empty():
from eventlet import hubs
hub = hubs.get_hub()
diff --git a/tests/ssl_test.py b/tests/ssl_test.py
index eb7dbeb..76fac24 100644
--- a/tests/ssl_test.py
+++ b/tests/ssl_test.py
@@ -2,9 +2,10 @@ from tests import LimitedTestCase, certificate_file, private_key_file
from tests import skip_if_no_ssl
from unittest import main
import eventlet
-from eventlet import util, coros, greenio
+from eventlet import util, greenio
+import signal
import socket
-import os
+
def listen_ssl_socket(address=('127.0.0.1', 0)):
sock = util.wrap_ssl(socket.socket(), certificate_file,
@@ -93,6 +94,47 @@ class SSLTest(LimitedTestCase):
client2.send('after')
server_coro.wait()
+ @skip_if_no_ssl
+ def test_sendall_cpu_usage(self):
+ """SSL socket.sendall() busy loop
+
+ https://bitbucket.org/which_linden/eventlet/issue/134/greenssl-performance-issues
+
+ Idea of this test is to check that GreenSSLSocket.sendall() does not busy loop
+ retrying .send() calls, but instead trampolines until socket is writeable.
+
+ BUFFER_SIZE and SENDALL_SIZE are magic numbers inferred through trial and error.
+ """
+ # Time limit resistant to busy loops
+ self.set_alarm(1)
+
+ stage_1 = eventlet.event.Event()
+ BUFFER_SIZE = 1000
+ SENDALL_SIZE = 100000
+
+ def serve(listener):
+ conn, _ = listener.accept()
+ conn.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, BUFFER_SIZE)
+ self.assertEqual(conn.read(8), 'request')
+ conn.write('response')
+
+ stage_1.wait()
+ conn.sendall('x' * SENDALL_SIZE)
+
+ server_sock = listen_ssl_socket()
+ server_coro = eventlet.spawn(serve, server_sock)
+
+ client_sock = eventlet.connect(server_sock.getsockname())
+ client_sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, BUFFER_SIZE)
+ client = util.wrap_ssl(client_sock)
+ client.write('request')
+ self.assertEqual(client.read(8), 'response')
+ stage_1.send()
+
+ self.assertIdleCpuUsage(0.2, 0.1)
+ server_coro.kill()
+
+
class SocketSSLTest(LimitedTestCase):
@skip_if_no_ssl
def test_greensslobject(self):
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment