[gedit] Implemented asynchronous reading and writing



commit cfb6dd10a0de6501a2d12031dfba1e99d2150e5c
Author: Jesse van den Kieboom <jesse icecrew nl>
Date:   Fri May 22 21:56:19 2009 +0200

    Implemented asynchronous reading and writing
    
    Before, writing to STDIN was done in one go, which fails for large buffers. This is now
    done asynchronously with 16K at the time, in an idle loop. Furthermore, STDERR and STDOUT
    are now non blocking and use 'read' instead of 'readline' to prevent blocking.
---
 plugins/externaltools/tools/capture.py |   85 ++++++++++++++++++++++++++++---
 1 files changed, 76 insertions(+), 9 deletions(-)

diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py
index 56930c8..afee587 100644
--- a/plugins/externaltools/tools/capture.py
+++ b/plugins/externaltools/tools/capture.py
@@ -22,11 +22,14 @@ import os, sys, signal
 import locale
 import subprocess
 import gobject
+import fcntl
 
 class Capture(gobject.GObject):
     CAPTURE_STDOUT = 0x01
     CAPTURE_STDERR = 0x02
     CAPTURE_BOTH   = 0x03
+    
+    WRITE_BUFFER_SIZE = 0x4000
 
     __gsignals__ = {
         'stdout-line'  : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
@@ -81,6 +84,8 @@ class Capture(gobject.GObject):
                 popen_args['stderr'] = subprocess.PIPE
 
         self.tried_killing = False
+        self.idle_write_id = 0
+        self.read_buffer = ''
         
         try:
             self.pipe = subprocess.Popen(self.command, **popen_args)
@@ -91,25 +96,64 @@ class Capture(gobject.GObject):
         
         # Signal
         self.emit('begin-execute')
-
-        # IO
-        if self.input_text is not None:
-            self.pipe.stdin.write(self.input_text)
-            self.pipe.stdin.close()
+        
         if self.flags & self.CAPTURE_STDOUT:
+            # Set non blocking
+            flags = fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK
+            fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_SETFL, flags)
+
             gobject.io_add_watch(self.pipe.stdout,
                                  gobject.IO_IN | gobject.IO_HUP,
                                  self.on_output)
+
         elif self.flags & self.CAPTURE_STDERR:
+            # Set non blocking
+            flags = fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK
+            fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_SETFL, flags)
+
             gobject.io_add_watch(self.pipe.stderr,
                                  gobject.IO_IN | gobject.IO_HUP,
                                  self.on_output)
 
+        # IO        
+        if self.input_text is not None:
+            # Write async, in chunks of something
+            self.write_buffer = str(self.input_text)
+
+            if self.idle_write_chunk():
+                self.idle_write_id = gobject.idle_add(self.idle_write_chunk)
+
         # Wait for the process to complete
         gobject.child_watch_add(self.pipe.pid, self.on_child_end)
 
+    def idle_write_chunk(self):
+        if not self.pipe:
+            self.idle_write_id = 0
+            return False
+
+        try:
+            l = len(self.write_buffer)
+            m = min(l, self.WRITE_BUFFER_SIZE)
+         
+            self.pipe.stdin.write(self.write_buffer[:m])
+            
+            if m == l:
+                self.write_buffer = ''
+                self.pipe.stdin.close()
+                
+                self.idle_write_id = 0
+                return False
+            else:
+                self.write_buffer = self.write_buffer[m:]
+                return True
+        except IOError:
+            self.pipe.stdin.close()
+            self.idle_write_id = 0
+
+            return False
+
     def on_output(self, source, condition):
-        line = source.readline()
+        line = source.read()
 
         if len(line) > 0:
             try:
@@ -119,18 +163,41 @@ class Capture(gobject.GObject):
                                locale.getdefaultlocale()[1],
                                'replace')
 
-            if not self.pipe or source == self.pipe.stdout:
-                self.emit('stdout-line', line)
+            self.read_buffer += line
+            lines = self.read_buffer.splitlines(True)
+            
+            if not lines[-1].endswith("\n"):
+                self.read_buffer = lines[-1]
+                lines = lines[0:-1]
             else:
-                self.emit('stderr-line', line)
+                self.read_buffer = ''
+
+            for line in lines:
+                if not self.pipe or source == self.pipe.stdout:
+                    self.emit('stdout-line', line)
+                else:
+                    self.emit('stderr-line', line)
+
             return True
         else:
+            if self.read_buffer:
+                if source == self.pipe.stdout:
+                    self.emit('stdout-line', self.read_buffer)
+                else:
+                    self.emit('stderr-line', self.read_buffer)
+                
+                self.read_buffer = ''
+            
             self.pipe = None
 
         return False
 
     def stop(self, error_code = -1):
         if self.pipe is not None:
+            if self.idle_write_id:
+                gobject.source_remove(self.idle_write_id)
+                self.idle_write_id = 0
+
             if not self.tried_killing:
                 os.kill(self.pipe.pid, signal.SIGTERM)
                 self.tried_killing = True



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]