7
7
> > MIT License https://github.com/palantir/python-jsonrpc-server/blob/0.2.0/LICENSE
8
8
> > Copyright 2018 Palantir Technologies, Inc.
9
9
"""
10
- import os
11
10
from abc import ABC , ABCMeta , abstractmethod
12
- from typing import List , Optional , Text
11
+ from typing import Optional , Text
13
12
14
13
# pylint: disable=broad-except
15
14
import anyio
16
15
from anyio .streams .buffered import BufferedByteReceiveStream
17
16
from anyio .streams .text import TextSendStream
18
- from tornado .gen import convert_yielded
19
17
from tornado .httputil import HTTPHeaders
20
18
from tornado .ioloop import IOLoop
21
19
from tornado .queues import Queue
22
- from traitlets import Float , Instance , Int , default
20
+ from traitlets import Instance , Int
23
21
from traitlets .config import LoggingConfigurable
24
22
from traitlets .traitlets import MetaHasTraits
25
23
@@ -48,15 +46,8 @@ async def close(self):
48
46
49
47
50
48
class LspStreamReader (LspStreamBase ):
51
- """Language Server Reader
49
+ """Language Server Reader"""
52
50
53
- Because non-blocking (but still synchronous) IO is used, rudimentary
54
- exponential backoff is used.
55
- """
56
-
57
- max_wait = Float (help = "maximum time to wait on idle stream" ).tag (config = True )
58
- min_wait = Float (0.05 , help = "minimum time to wait on idle stream" ).tag (config = True )
59
- next_wait = Float (0.05 , help = "next time to wait on idle stream" ).tag (config = True )
60
51
receive_max_bytes = Int (
61
52
65536 ,
62
53
help = "the maximum size a header line send by the language server may have" ,
@@ -74,32 +65,12 @@ async def close(self):
74
65
await self .stream .aclose ()
75
66
self .log .debug ("%s closed" , self )
76
67
77
- @default ("max_wait" )
78
- def _default_max_wait (self ):
79
- return 0.1 if os .name == "nt" else self .min_wait * 2
80
-
81
- async def sleep (self ):
82
- """Simple exponential backoff for sleeping"""
83
- self .next_wait = min (self .next_wait * 2 , self .max_wait )
84
- await anyio .sleep (self .next_wait )
85
-
86
- def wake (self ):
87
- """Reset the wait time"""
88
- self .wait = self .min_wait
89
-
90
68
async def read (self ) -> None :
91
69
"""Read from a Language Server until it is closed"""
92
70
while True :
93
71
message = None
94
72
try :
95
73
message = await self .read_one ()
96
-
97
- if not message :
98
- await self .sleep ()
99
- continue
100
- else :
101
- self .wake ()
102
-
103
74
IOLoop .current ().add_callback (self .queue .put_nowait , message )
104
75
except anyio .ClosedResourceError :
105
76
# stream was closed -> terminate
@@ -109,89 +80,48 @@ async def read(self) -> None:
109
80
self .log .exception (
110
81
"%s couldn't enqueue message: %s (%s)" , self , message , e
111
82
)
112
- await self .sleep ()
113
-
114
- async def _read_content (
115
- self , length : int , max_parts = 1000 , max_empties = 200
116
- ) -> Optional [bytes ]:
117
- """Read the full length of the message unless exceeding max_parts or
118
- max_empties empty reads occur.
119
83
120
- See https://github.com/jupyter-lsp/jupyterlab-lsp/issues/450
121
-
122
- Crucial docs or read():
123
- "If the argument is positive, and the underlying raw
124
- stream is not interactive, multiple raw reads may be issued
125
- to satisfy the byte count (unless EOF is reached first)"
84
+ async def _read_content (self , length : int ) -> Optional [bytes ]:
85
+ """Read the full length of the message.
126
86
127
87
Args:
128
88
- length: the content length
129
- - max_parts: prevent absurdly long messages (1000 parts is several MBs):
130
- 1 part is usually sufficient but not enough for some long
131
- messages 2 or 3 parts are often needed.
132
89
"""
133
- raw = None
134
- raw_parts : List [bytes ] = []
135
- received_size = 0
136
- while received_size < length and len (raw_parts ) < max_parts and max_empties > 0 :
137
- part = None
138
- try :
139
- part = await self .stream .receive_exactly (length - received_size )
140
- except anyio .IncompleteRead : # pragma: no cover
141
- pass
142
- if part is None : # pragma: no cover
143
- max_empties -= 1
144
- await self .sleep ()
145
- continue
146
- received_size += len (part )
147
- raw_parts .append (part )
148
-
149
- if raw_parts :
150
- raw = b"" .join (raw_parts )
151
- if len (raw ) != length : # pragma: no cover
152
- self .log .warning (
153
- f"Readout and content-length mismatch: { len (raw )} vs { length } ;"
154
- f"remaining empties: { max_empties } ; remaining parts: { max_parts } "
155
- )
156
-
157
- return raw
90
+ try :
91
+ return await self .stream .receive_exactly (length )
92
+ except anyio .IncompleteRead : # pragma: no cover
93
+ # resource has been closed before the requested bytes could be retrieved
94
+ # -> signal recource closed
95
+ raise anyio .ClosedResourceError
158
96
159
97
async def read_one (self ) -> Text :
160
98
"""Read a single message"""
161
99
message = ""
162
100
headers = HTTPHeaders ()
163
101
164
- line = await convert_yielded ( self ._readline () )
102
+ line = await self ._readline ()
165
103
166
104
if line :
167
105
while line and line .strip ():
168
106
headers .parse_line (line )
169
- line = await convert_yielded ( self ._readline () )
107
+ line = await self ._readline ()
170
108
171
109
content_length = int (headers .get ("content-length" , "0" ))
172
110
173
111
if content_length :
174
112
raw = await self ._read_content (length = content_length )
175
- if raw is not None :
176
- message = raw .decode ("utf-8" ).strip ()
177
- else : # pragma: no cover
178
- self .log .warning (
179
- "%s failed to read message of length %s" ,
180
- self ,
181
- content_length ,
182
- )
113
+ message = raw .decode ("utf-8" ).strip ()
183
114
184
115
return message
185
116
186
117
async def _readline (self ) -> Text :
187
- """Read a line (or immediately return None) """
118
+ """Read a line"""
188
119
try :
189
120
# use same max_bytes as is default for receive for now. It seems there is no
190
121
# way of getting the bytes read until max_bytes is reached, so we cannot
191
122
# iterate the receive_until call with smaller max_bytes values
192
- async with anyio .move_on_after (0.2 ):
193
- line = await self .stream .receive_until (b"\r \n " , self .receive_max_bytes )
194
- return line .decode ("utf-8" ).strip ()
123
+ line = await self .stream .receive_until (b"\r \n " , self .receive_max_bytes )
124
+ return line .decode ("utf-8" ).strip ()
195
125
except anyio .IncompleteRead :
196
126
# resource has been closed before the requested bytes could be retrieved
197
127
# -> signal recource closed
@@ -225,7 +155,7 @@ async def write(self) -> None:
225
155
try :
226
156
n_bytes = len (message .encode ("utf-8" ))
227
157
response = "Content-Length: {}\r \n \r \n {}" .format (n_bytes , message )
228
- await convert_yielded ( self ._write_one (response ) )
158
+ await self ._write_one (response )
229
159
except (
230
160
anyio .ClosedResourceError ,
231
161
anyio .BrokenResourceError ,
0 commit comments