44# pylint: disable=too-many-lines,duplicate-code
55
66import os
7+ import select
78import time
89import errno
910from enum import Enum
5152 RECONNECT_DELAY ,
5253 DEFAULT_TRANSPORT ,
5354 SSL_TRANSPORT ,
55+ DEFAULT_HOST ,
56+ DEFAULT_PORT ,
57+ DEFAULT_SOCKET_FD ,
5458 DEFAULT_SSL_KEY_FILE ,
5559 DEFAULT_SSL_CERT_FILE ,
5660 DEFAULT_SSL_CA_FILE ,
@@ -594,7 +598,10 @@ class Connection(ConnectionInterface):
594598 :value: :exc:`~tarantool.error.CrudModuleError`
595599 """
596600
597- def __init__ (self , host , port ,
601+ def __init__ (self ,
602+ host = DEFAULT_HOST ,
603+ port = DEFAULT_PORT ,
604+ socket_fd = DEFAULT_SOCKET_FD ,
598605 user = None ,
599606 password = None ,
600607 socket_timeout = SOCKET_TIMEOUT ,
@@ -623,8 +630,11 @@ def __init__(self, host, port,
623630 Unix sockets.
624631 :type host: :obj:`str` or :obj:`None`
625632
626- :param port: Server port or Unix socket path.
627- :type port: :obj:`int` or :obj:`str`
633+ :param port: Server port, or Unix socket path.
634+ :type port: :obj:`int` or :obj:`str` or :obj:`None`
635+
636+ :param socket_fd: socket fd number.
637+ :type socket_fd: :obj:`int` or :obj:`None`
628638
629639 :param user: User name for authentication on the Tarantool
630640 server.
@@ -804,6 +814,18 @@ def __init__(self, host, port,
804814 """
805815 # pylint: disable=too-many-arguments,too-many-locals,too-many-statements
806816
817+ if host is None and port is None and socket_fd is None :
818+ raise ConfigurationError ("need to specify host/port, "
819+ "port (in case of Unix sockets) "
820+ "or socket_fd" )
821+
822+ if socket_fd is not None and (host is not None or port is not None ):
823+ raise ConfigurationError ("specifying both socket_fd and host/port is not allowed" )
824+
825+ if host is not None and port is None :
826+ raise ConfigurationError ("when specifying host, "
827+ "it is also necessary to specify port" )
828+
807829 if msgpack .version >= (1 , 0 , 0 ) and encoding not in (None , 'utf-8' ):
808830 raise ConfigurationError ("msgpack>=1.0.0 only supports None and "
809831 + "'utf-8' encoding option values" )
@@ -820,6 +842,7 @@ def __init__(self, host, port,
820842 recv .restype = ctypes .c_int
821843 self .host = host
822844 self .port = port
845+ self .socket_fd = socket_fd
823846 self .user = user
824847 self .password = password
825848 self .socket_timeout = socket_timeout
@@ -897,10 +920,37 @@ def connect_basic(self):
897920 :meta private:
898921 """
899922
900- if self .host is None :
901- self .connect_unix ()
902- else :
923+ if self .socket_fd is not None :
924+ self .connect_socket_fd ()
925+ elif self . host is not None :
903926 self .connect_tcp ()
927+ else :
928+ self .connect_unix ()
929+
930+ def connect_socket_fd (self ):
931+ """
932+ Establish a connection using an existing socket fd.
933+
934+ +---------------------+--------------------------+-------------------------+
935+ | socket_fd / timeout | >= 0 | `None` |
936+ +=====================+==========================+=========================+
937+ | blocking | Set non-blocking socket | Don't change, `select` |
938+ | | lib call `select` | isn't needed |
939+ +---------------------+--------------------------+-------------------------+
940+ | non-blocking | Don't change, socket lib | Don't change, call |
941+ | | call `select` | `select` ourselves |
942+ +---------------------+--------------------------+-------------------------+
943+
944+ :meta private:
945+ """
946+
947+ self .connected = True
948+ if self ._socket :
949+ self ._socket .close ()
950+
951+ self ._socket = socket .socket (fileno = self .socket_fd )
952+ if self .socket_timeout is not None :
953+ self ._socket .settimeout (self .socket_timeout )
904954
905955 def connect_tcp (self ):
906956 """
@@ -1124,6 +1174,11 @@ def _recv(self, to_read):
11241174 while to_read > 0 :
11251175 try :
11261176 tmp = self ._socket .recv (to_read )
1177+ except BlockingIOError :
1178+ ready , _ , _ = select .select ([self ._socket .fileno ()], [], [], self .socket_timeout )
1179+ if not ready :
1180+ raise NetworkError (TimeoutError ()) # pylint: disable=raise-missing-from
1181+ continue
11271182 except OverflowError as exc :
11281183 self ._socket .close ()
11291184 err = socket .error (
@@ -1163,6 +1218,41 @@ def _read_response(self):
11631218 # Read the packet
11641219 return self ._recv (length )
11651220
1221+ def _sendall (self , bytes_to_send ):
1222+ """
1223+ Sends bytes to the transport (socket).
1224+
1225+ :param bytes_to_send: Message to send.
1226+ :type bytes_to_send: :obj:`bytes`
1227+
1228+ :raise: :exc:`~tarantool.error.NetworkError`
1229+
1230+ :meta private:
1231+ """
1232+
1233+ total_sent = 0
1234+ while total_sent < len (bytes_to_send ):
1235+ try :
1236+ sent = self ._socket .send (bytes_to_send [total_sent :])
1237+ if sent == 0 :
1238+ err = socket .error (
1239+ errno .ECONNRESET ,
1240+ "Lost connection to server during query"
1241+ )
1242+ raise NetworkError (err )
1243+ total_sent += sent
1244+ except BlockingIOError as exc :
1245+ total_sent += exc .characters_written
1246+ _ , ready , _ = select .select ([], [self ._socket .fileno ()], [], self .socket_timeout )
1247+ if not ready :
1248+ raise NetworkError (TimeoutError ()) # pylint: disable=raise-missing-from
1249+ except socket .error as exc :
1250+ err = socket .error (
1251+ errno .ECONNRESET ,
1252+ "Lost connection to server during query"
1253+ )
1254+ raise NetworkError (err ) from exc
1255+
11661256 def _send_request_wo_reconnect (self , request , on_push = None , on_push_ctx = None ):
11671257 """
11681258 Send request without trying to reconnect.
@@ -1191,7 +1281,7 @@ def _send_request_wo_reconnect(self, request, on_push=None, on_push_ctx=None):
11911281 response = None
11921282 while True :
11931283 try :
1194- self ._socket . sendall (bytes (request ))
1284+ self ._sendall (bytes (request ))
11951285 response = request .response_class (self , self ._read_response ())
11961286 break
11971287 except SchemaReloadException as exc :
0 commit comments