| 279 | |
| 280 | |
| 281 | class StreamCapturer(Thread): |
| 282 | daemon = True # Don't hang if main thread crashes |
| 283 | started = False |
| 284 | def __init__(self, echo=False): |
| 285 | super(StreamCapturer, self).__init__() |
| 286 | self.echo = echo |
| 287 | self.streams = [] |
| 288 | self.buffer = BytesIO() |
| 289 | self.readfd, self.writefd = os.pipe() |
| 290 | self.buffer_lock = Lock() |
| 291 | self.stop = Event() |
| 292 | |
| 293 | def run(self): |
| 294 | self.started = True |
| 295 | |
| 296 | while not self.stop.is_set(): |
| 297 | chunk = os.read(self.readfd, 1024) |
| 298 | |
| 299 | with self.buffer_lock: |
| 300 | self.buffer.write(chunk) |
| 301 | if self.echo: |
| 302 | sys.stdout.write(decode(chunk)) |
| 303 | |
| 304 | os.close(self.readfd) |
| 305 | os.close(self.writefd) |
| 306 | |
| 307 | def reset_buffer(self): |
| 308 | with self.buffer_lock: |
| 309 | self.buffer.truncate(0) |
| 310 | self.buffer.seek(0) |
| 311 | |
| 312 | def get_buffer(self): |
| 313 | with self.buffer_lock: |
| 314 | return self.buffer.getvalue() |
| 315 | |
| 316 | def ensure_started(self): |
| 317 | if not self.started: |
| 318 | self.start() |
| 319 | |
| 320 | def halt(self): |
| 321 | """Safely stop the thread.""" |
| 322 | if not self.started: |
| 323 | return |
| 324 | |
| 325 | self.stop.set() |
| 326 | os.write(self.writefd, b'\0') # Ensure we're not locked in a read() |
| 327 | self.join() |
| 328 | |
| 329 | class SubprocessStreamCapturePlugin(Plugin): |
| 330 | name='subprocstreams' |