Last active
December 10, 2015 21:58
-
-
Save temoto/4498139 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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