@@ -31,8 +31,8 @@ class LspStdIoBase(LoggingConfigurable):
31
31
executor = None
32
32
33
33
stream = Instance (
34
- io .BufferedIOBase , help = "the stream to read/write"
35
- ) # type: io.BufferedIOBase
34
+ io .RawIOBase , help = "the stream to read/write"
35
+ ) # type: io.RawIOBase
36
36
queue = Instance (Queue , help = "queue to get/put" )
37
37
38
38
def __repr__ (self ): # pragma: no cover
@@ -61,7 +61,7 @@ class LspStdIoReader(LspStdIoBase):
61
61
62
62
@default ("max_wait" )
63
63
def _default_max_wait (self ):
64
- return 2.0 if os .name == "nt" else self .min_wait
64
+ return 0.1 if os .name == "nt" else self .min_wait * 2
65
65
66
66
async def sleep (self ):
67
67
"""Simple exponential backoff for sleeping"""
@@ -93,12 +93,17 @@ async def read(self) -> None:
93
93
self .wake ()
94
94
95
95
IOLoop .current ().add_callback (self .queue .put_nowait , message )
96
- except Exception : # pragma: no cover
97
- self .log .exception ("%s couldn't enqueue message: %s" , self , message )
96
+ except Exception as e : # pragma: no cover
97
+ self .log .exception (
98
+ "%s couldn't enqueue message: %s (%s)" , self , message , e
99
+ )
98
100
await self .sleep ()
99
101
100
- def _read_content (self , length : int , max_parts = 1000 ) -> Optional [bytes ]:
101
- """Read the full length of the message unless exceeding max_parts.
102
+ async def _read_content (
103
+ self , length : int , max_parts = 1000 , max_empties = 200
104
+ ) -> Optional [bytes ]:
105
+ """Read the full length of the message unless exceeding max_parts or
106
+ max_empties empty reads occur.
102
107
103
108
See https://github.com/krassowski/jupyterlab-lsp/issues/450
104
109
@@ -116,19 +121,27 @@ def _read_content(self, length: int, max_parts=1000) -> Optional[bytes]:
116
121
raw = None
117
122
raw_parts : List [bytes ] = []
118
123
received_size = 0
119
- while received_size < length and len (raw_parts ) < max_parts :
120
- part = self .stream .read (length )
124
+ while received_size < length and len (raw_parts ) < max_parts and max_empties > 0 :
125
+ part = None
126
+ try :
127
+ part = self .stream .read (length )
128
+ except OSError : # pragma: no cover
129
+ pass
121
130
if part is None :
122
- break # pragma: no cover
131
+ max_empties -= 1
132
+ await self .sleep ()
133
+ continue
123
134
received_size += len (part )
124
135
raw_parts .append (part )
125
136
126
137
if raw_parts :
127
138
raw = b"" .join (raw_parts )
128
139
if len (raw ) != length : # pragma: no cover
129
140
self .log .warning (
130
- f"Readout and content-length mismatch:" f" { len (raw )} vs { length } "
141
+ f"Readout and content-length mismatch: { len (raw )} vs { length } ;"
142
+ f"remaining empties: { max_empties } ; remaining parts: { max_parts } "
131
143
)
144
+
132
145
return raw
133
146
134
147
async def read_one (self ) -> Text :
@@ -146,24 +159,15 @@ async def read_one(self) -> Text:
146
159
content_length = int (headers .get ("content-length" , "0" ))
147
160
148
161
if content_length :
149
- raw = None
150
- retries = 5
151
- while raw is None and retries :
152
- try :
153
- raw = self ._read_content (length = content_length )
154
- except OSError : # pragma: no cover
155
- raw = None
156
- if raw is None : # pragma: no cover
157
- self .log .warning (
158
- "%s failed to read message of length %s" ,
159
- self ,
160
- content_length ,
161
- )
162
- await self .sleep ()
163
- retries -= 1
164
- else :
165
- message = raw .decode ("utf-8" ).strip ()
166
- break
162
+ raw = await self ._read_content (length = content_length )
163
+ if raw is not None :
164
+ message = raw .decode ("utf-8" ).strip ()
165
+ else : # pragma: no cover
166
+ self .log .warning (
167
+ "%s failed to read message of length %s" ,
168
+ self ,
169
+ content_length ,
170
+ )
167
171
168
172
return message
169
173
0 commit comments