Browse Source

Another try for issue #30

blocking mode with select() and bufsize=1, buggy in python 2.x:
close() does not notify select() for EOF
Matthew Wang 11 years ago
parent
commit
d4ed688e5c
1 changed files with 42 additions and 31 deletions
  1. 42
    31
      cdiff.py

+ 42
- 31
cdiff.py View File

26
 import re
26
 import re
27
 import signal
27
 import signal
28
 import subprocess
28
 import subprocess
29
-import fcntl
29
+import select
30
 import os
30
 import os
31
 import difflib
31
 import difflib
32
 
32
 
226
 
226
 
227
 
227
 
228
 class PatchStreamForwarder(object):
228
 class PatchStreamForwarder(object):
229
-    """A non-block stream forwarder.  Note input stream is non-seekable, and
230
-    upstream has eaten some lines.
229
+    """A blocking stream forwarder use `select` and line buffered mode.  Feed
230
+    input stream to a diff format translator and read output stream from it.
231
+    Note input stream is non-seekable, and upstream has eaten some lines.
231
     """
232
     """
232
     def __init__(self, istream, translator):
233
     def __init__(self, istream, translator):
233
         assert isinstance(istream, PatchStream)
234
         assert isinstance(istream, PatchStream)
234
-        self._istream = istream
235
-        self._translator = translator
235
+        assert isinstance(translator, subprocess.Popen)
236
+        self._istream = iter(istream)
237
+        self._in = translator.stdin
238
+        self._out = translator.stdout
236
 
239
 
237
-        self._set_non_block(self._translator.stdin)
238
-        self._set_non_block(self._translator.stdout)
240
+    def _can_read(self, timeout=0):
241
+        return select.select([self._out.fileno()], [], [], timeout)[0]
239
 
242
 
240
-        self._forward_until_block()
241
-
242
-    def _set_non_block(self, hdl):
243
-        fd = hdl.fileno()
244
-        fl = fcntl.fcntl(fd, fcntl.F_GETFL)
245
-        fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
246
-
247
-    def _forward_until_block(self):
248
-        for line in self._istream:
249
-            try:
250
-                self._translator.stdin.write(line.encode('utf-8'))
251
-            except IOError:
252
-                break       # EAGAIN
253
-        else:
254
-            self._translator.stdin.close()
243
+    def _forward_line(self):
244
+        try:
245
+            line = next(self._istream)
246
+            self._in.write(line.encode('utf-8'))
247
+        except StopIteration:
248
+            # XXX: close() does not notify select() for EOF event in python
249
+            # 2.x, one of these two interface must be buggy
250
+            #
251
+            # Sending EOF manually does not work either
252
+            #
253
+            #print('StopIteration, closing (sending EOF)')
254
+            #self._in.write('\x1a'.encode('utf-8'))
255
+            self._in.close()
255
 
256
 
256
     def __iter__(self):
257
     def __iter__(self):
257
         while True:
258
         while True:
258
-            try:
259
-                line = self._translator.stdout.readline()
260
-                if not line:
259
+            if self._can_read():
260
+                line = self._out.readline()
261
+                if line:
262
+                    yield line
263
+                else:
264
+                    #print('got EOF')
265
+                    return
266
+            elif not self._in.closed:
267
+                self._forward_line()
268
+            else:
269
+                #print('no data to read and istream closed')
270
+                # XXX: `close` or `select` seems buggy in python 2.x, select
271
+                # does not tell ready event on _translator.stdout anymore once
272
+                # the stdin is closed.  Just add a timeout here to detect, when
273
+                # that happen the remaining in output stream will be discarded
274
+                #
275
+                if not self._can_read(0.5):
261
                     return
276
                     return
262
-                yield line
263
-            except IOError:
264
-                if not self._translator.stdin.closed:
265
-                    self._forward_until_block()
266
-                continue    # EAGAIN
267
 
277
 
268
 
278
 
269
 class DiffParser(object):
279
 class DiffParser(object):
283
             #
293
             #
284
             self._type = 'context'
294
             self._type = 'context'
285
             try:
295
             try:
296
+                # Use line buffered mode so that to readline() in block mode
286
                 self._translator = subprocess.Popen(
297
                 self._translator = subprocess.Popen(
287
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
298
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
288
-                    stdout=subprocess.PIPE)
299
+                    stdout=subprocess.PIPE, bufsize=1)
289
             except OSError:
300
             except OSError:
290
                 raise SystemExit('*** Context diff support depends on '
301
                 raise SystemExit('*** Context diff support depends on '
291
                                  'filterdiff')
302
                                  'filterdiff')