142142
143143
144144class _HiveClient :
145- """Helper class to nicely open and close the transport."""
145+ """Helper class to nicely open and close the transport.
146146
147- _transport : TTransport
147+ ``TSaslClientTransport`` instances are single-use: ``close()`` disposes
148+ the SASL session and the same instance cannot be reopened. The
149+ transport is therefore created lazily in ``__enter__`` rather than held
150+ on the class, which lets the ``_HiveClient`` itself live for the
151+ duration of a ``HiveCatalog`` while still honouring the SASL transport
152+ lifecycle.
153+ """
154+
155+ _transport : "TTransport | None"
148156 _ugi : list [str ] | None
149157
150158 def __init__ (
@@ -158,7 +166,7 @@ def __init__(
158166 self ._kerberos_auth = kerberos_auth
159167 self ._kerberos_service_name = kerberos_service_name
160168 self ._ugi = ugi .split (":" ) if ugi else None
161- self ._transport = self . _init_thrift_transport ()
169+ self ._transport = None
162170
163171 def _init_thrift_transport (self ) -> TTransport :
164172 url_parts = urlparse (self ._uri )
@@ -169,31 +177,28 @@ def _init_thrift_transport(self) -> TTransport:
169177 return TTransport .TSaslClientTransport (socket , host = url_parts .hostname , service = self ._kerberos_service_name )
170178
171179 def _client (self ) -> Client :
180+ assert self ._transport is not None # set by __enter__
172181 protocol = TBinaryProtocol .TBinaryProtocol (self ._transport )
173182 client = Client (protocol )
174183 if self ._ugi :
175184 client .set_ugi (* self ._ugi )
176185 return client
177186
178187 def __enter__ (self ) -> Client :
179- """Make sure the transport is initialized and open."""
180- if not self ._transport .isOpen ():
181- try :
182- self ._transport .open ()
183- except (TypeError , TTransport .TTransportException ):
184- # Close the old transport before reinitializing to prevent resource leaks
185- try :
186- self ._transport .close ()
187- except Exception :
188- pass
189- # reinitialize _transport
190- self ._transport = self ._init_thrift_transport ()
191- self ._transport .open ()
192- return self ._client () # recreate the client
188+ """Initialize and open a fresh transport for this entry.
189+
190+ A new ``TSaslClientTransport`` is created on every entry because
191+ the underlying SASL session cannot be reused after ``close()``.
192+ Reusing a closed transport would leave a half-opened TCP socket
193+ and SASL handshake EOF noise on the server.
194+ """
195+ self ._transport = self ._init_thrift_transport ()
196+ self ._transport .open ()
197+ return self ._client ()
193198
194199 def __exit__ (self , exctype : type [BaseException ] | None , excinst : BaseException | None , exctb : TracebackType | None ) -> None :
195200 """Close transport if it was opened."""
196- if self ._transport .isOpen ():
201+ if self ._transport is not None and self . _transport .isOpen ():
197202 self ._transport .close ()
198203
199204
0 commit comments