Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/core/IronPython.Modules/_socket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,18 +278,18 @@ private void _close() {
+ "\n"
+ "Raises socket.error if the socket has been closed, the socket is listening, or\n"
+ "another connection error occurred."
+ "\n"
+ "Difference from CPython: connect() does not support timeouts in blocking mode.\n"
+ "If a timeout is set and the socket is in blocking mode, connect() will block\n"
+ "indefinitely until a connection is made or an error occurs."
)]
public void connect([NotNone] PythonTuple address) {
IPEndPoint remoteEP = TupleToEndPoint(_context, address, _socket.AddressFamily, out _hostName);
bool connected;
try {
_socket.Connect(remoteEP);
connected = TryConnect(remoteEP);
} catch (Exception e) {
throw MakeException(_context, e);
}
if (!connected) {
throw PythonExceptions.CreateThrowable(timeout(_context), "timed out");
}
}

[Documentation("connect_ex(address) -> error_code\n\n"
Expand All @@ -299,21 +299,36 @@ public void connect([NotNone] PythonTuple address) {
+ "will still be raised.\n"
+ "\n"
+ "A return value of 0 indicates that the connect call was successful."
+ "\n"
+ "Difference from CPython: connect_ex() does not support timeouts in blocking\n"
+ "mode. If a timeout is set and the socket is in blocking mode, connect_ex() will\n"
+ "block indefinitely until a connection is made or an error occurs."
)]
public int connect_ex([NotNone] PythonTuple address) {
IPEndPoint remoteEP = TupleToEndPoint(_context, address, _socket.AddressFamily, out _hostName);
try {
_socket.Connect(remoteEP);
if (!TryConnect(remoteEP)) {
return PythonErrno.EWOULDBLOCK;
}
} catch (SocketException ex) {
return !ClrModule.IsMono ? ex.NativeErrorCode : MapMonoSocketErrorToErrno(ex.SocketErrorCode);
}
return PythonErrno.ENOERROR;
}

/// <summary>
/// Returns false if the timeout elapsed. The attempt is then left pending, as in CPython.
/// </summary>
private bool TryConnect(IPEndPoint remoteEP) {
if (_timeout == 0) {
_socket.Connect(remoteEP);
return true;
}

IAsyncResult result = _socket.BeginConnect(remoteEP, null, null);
if (!result.AsyncWaitHandle.WaitOne(_timeout)) {
return false;
}
_socket.EndConnect(result);
return true;
}

public long detach() {
var fd = fileno();
_socket = null!; // TODO: this is wrong, no null checks on _socket anywhere!
Expand Down
53 changes: 53 additions & 0 deletions tests/suite/modules/network_related/test__socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# test _socket
#

import errno
import os
import _socket
import sys
Expand Down Expand Up @@ -692,4 +693,56 @@ def server_thread():

f.close()

class SocketTimeoutTest(IronPythonTestCase):
BLACKHOLE = ('10.255.255.1', 22) # an unroutable address

def test_create_connection_timeout(self):
start = time.time()
try:
socket.create_connection(self.BLACKHOLE, timeout=1).close()
except socket.timeout:
pass
except OSError as e:
self.skipTest("create_connection failed without timing out: %s" % e)
else:
self.skipTest("create_connection succeeded")
self.assertLess(time.time() - start, 5)

def test_connect_timeout(self):
with socket.socket() as s:
s.settimeout(1)
start = time.time()
try:
s.connect(self.BLACKHOLE)
except socket.timeout:
pass
except OSError as e:
self.skipTest("connect failed without timing out: %s" % e)
else:
self.skipTest("connect succeeded")
self.assertLess(time.time() - start, 5)
self.assertEqual(s.gettimeout(), 1)

def test_connect_ex_timeout(self):
with socket.socket() as s:
s.settimeout(1)
start = time.time()
err = s.connect_ex(self.BLACKHOLE)
self.assertLess(time.time() - start, 5)
if err != errno.EWOULDBLOCK:
self.skipTest("connect_ex returned %d instead of timing out" % err)

def test_connect_within_timeout(self):
# make sure we still connect when timeout is set
with socket.socket() as server:
server.bind(('127.0.0.1', 0))
server.listen(1)
with socket.socket() as s:
s.settimeout(5)
s.connect(server.getsockname())
conn, _ = server.accept()
with conn:
s.sendall(b'abc')
self.assertEqual(conn.recv(3), b'abc')

run_test(__name__)
4 changes: 4 additions & 0 deletions tests/suite/stdlib/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def load_tests(loader, standard_tests, pattern):
]

skip_tests = []
if sys.version_info >= (3, 6):
skip_tests += [
test.test_support.TestSupport('test_fd_count'), # flaky: a finalizer can close a leaked fd between the two counts
]

return generate_suite(tests, failing_tests, skip_tests)

Expand Down
Loading