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 10 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,7 +26,7 @@ if sys.hexversion < 0x02050000:
26 26
 import re
27 27
 import signal
28 28
 import subprocess
29
-import fcntl
29
+import select
30 30
 import os
31 31
 import difflib
32 32
 
@@ -226,44 +226,54 @@ class PatchStream(object):
226 226
 
227 227
 
228 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 233
     def __init__(self, istream, translator):
233 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 257
     def __iter__(self):
257 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 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 279
 class DiffParser(object):
@@ -283,9 +293,10 @@ class DiffParser(object):
283 293
             #
284 294
             self._type = 'context'
285 295
             try:
296
+                # Use line buffered mode so that to readline() in block mode
286 297
                 self._translator = subprocess.Popen(
287 298
                     ['filterdiff', '--format=unified'], stdin=subprocess.PIPE,
288
-                    stdout=subprocess.PIPE)
299
+                    stdout=subprocess.PIPE, bufsize=1)
289 300
             except OSError:
290 301
                 raise SystemExit('*** Context diff support depends on '
291 302
                                  'filterdiff')