2121import logging
2222import socket
2323from concurrent .futures import ThreadPoolExecutor
24- from typing import Tuple , Dict , TypedDict , Optional
24+ from typing import Dict , Optional , Tuple , TypedDict
2525
2626import socks
2727
@@ -45,13 +45,24 @@ class Proxy(TypedDict):
4545class TCP :
4646 TIMEOUT = 10
4747
48- def __init__ (self , ipv6 : bool , proxy : Proxy , loop : Optional [asyncio .AbstractEventLoop ] = None ) -> None :
48+ def __init__ (
49+ self ,
50+ ipv6 : bool = False ,
51+ proxy : Proxy = None ,
52+ crypto_executor_workers : int = 1 ,
53+ loop : Optional [asyncio .AbstractEventLoop ] = None ,
54+ ) -> None :
4955 self .ipv6 = ipv6
5056 self .proxy = proxy
5157
58+ self .crypto_executor_workers = crypto_executor_workers
59+ self .crypto_executor = ThreadPoolExecutor (max_workers = self .crypto_executor_workers , thread_name_prefix = "CryptoWorker" )
60+
5261 self .reader : Optional [asyncio .StreamReader ] = None
5362 self .writer : Optional [asyncio .StreamWriter ] = None
5463
64+ self .marker_event = asyncio .Event ()
65+
5566 self .lock = asyncio .Lock ()
5667
5768 if isinstance (loop , asyncio .AbstractEventLoop ):
@@ -95,8 +106,7 @@ async def _connect_via_proxy(
95106 )
96107 sock .settimeout (TCP .TIMEOUT )
97108
98- with ThreadPoolExecutor () as executor :
99- await self .loop .run_in_executor (executor , sock .connect , destination )
109+ await self .loop .run_in_executor (self .crypto_executor , sock .connect , destination )
100110
101111 sock .setblocking (False )
102112
@@ -124,27 +134,40 @@ async def _connect(self, destination: Tuple[str, int]) -> None:
124134
125135 async def connect (self , address : Tuple [str , int ]) -> None :
126136 try :
127- await asyncio .wait_for (self ._connect (address ), TCP .TIMEOUT )
137+ await asyncio .wait_for (self ._connect (address ), timeout = TCP .TIMEOUT )
128138 except asyncio .TimeoutError : # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11
129139 raise TimeoutError ("Connection timed out" )
130140
131141 async def close (self ) -> None :
132- if self .writer is None :
133- return None
142+ async with self .lock :
143+ if self .writer is None or self .writer .is_closing ():
144+ return None
134145
135- try :
136- self .writer .close ()
137- await asyncio .wait_for (self .writer .wait_closed (), TCP .TIMEOUT )
138- except Exception as e :
139- log .info ("Close exception: %s %s" , type (e ).__name__ , e )
140- finally :
141- self .writer = None
142-
143- async def send (self , data : bytes ) -> None :
146+ try :
147+ if self .writer .transport is not None :
148+ self .writer .transport .abort ()
149+
150+ self .writer .close ()
151+
152+ await asyncio .wait_for (self .writer .wait_closed (), timeout = TCP .TIMEOUT )
153+ except asyncio .TimeoutError :
154+ log .warning ("Disconnect timed out" )
155+ except Exception as e :
156+ log .info ("Close exception: %s %s" , type (e ).__name__ , e )
157+ finally :
158+ self .writer = None
159+
160+ async def send (self , data : bytes , wait_for_marker : bool = True ) -> None :
144161 async with self .lock :
145162 if self .writer is None or self .writer .is_closing ():
146163 return None
147164
165+ if wait_for_marker :
166+ try :
167+ await asyncio .wait_for (self .marker_event .wait (), timeout = TCP .TIMEOUT )
168+ except asyncio .TimeoutError :
169+ raise TimeoutError
170+
148171 try :
149172 self .writer .write (data )
150173 await self .writer .drain ()
@@ -162,7 +185,7 @@ async def recv(self, length: int = 0) -> Optional[bytes]:
162185 try :
163186 chunk = await asyncio .wait_for (
164187 self .reader .read (length - len (data )),
165- TCP .TIMEOUT
188+ timeout = TCP .TIMEOUT
166189 )
167190 except (OSError , asyncio .TimeoutError ):
168191 return None
0 commit comments