prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  mitmdump jumps to 100% CPU when parent process exits
  #### Problem Description
  
  It took me two days to make this reproduce in isolation. I hope someone with Python skills can figure out what is happening here. Depending on what the root cause is this might not even be related to my funny architecture.
  
  I'm spawning `mitmdump` from Node.js. If the node process exits mitmdump will be re-assigned to become a child of `systemd` (some unix wizardry). It will then immediately jump to 100% CPU and stay there. This _only_ happens when an addon is using at least one network event (go figure...). E.g. I'm using `client_connected` (works with `clientconnect` on v6 as well). If the addon is only using sth. like `running` the bug does not occur. Even better: if the addon originally only has "running" nothing bad happens. But if I then add a `client_connected` and save the file (and the addon is automatically reloaded) it will instantly jump to 100% CPU.
  
  My guess is that it might be related to stdout and the switcheroo with the parent process? In my actual architecture the mitmdump process will poll the parent via gRPC every second and shutdown if it's gone. But the 100% CPU prevents that.
  
  Update: while trying to write down the exact steps it turns out this might only reproduce via local venv and and not if you download the binary. I'm not sure, it's confusing. I'm confused. But I have video proof, so I'm not completely insane.
  
  #### Steps to reproduce the behavior:
  
  index.js
  
  ```js
  const path = require('path');
  const { spawn } = require('child_process');
  
  function handleStdOut(data) {
    console.log(`mitmdump stdout: ${data}`);
  }
  
  function handleStdError(data) {
    console.error(`mitmdump stderr: ${data}`);
  }
  
  function handleExit(code) {
    console.log(`mitm process exited with code ${code}`);
  }
  
  const mitm = spawn(
    // Adjust this path
    '/home/alex/Projects/Bandsalat/src/forks/mitmproxy/venv/bin/mitmdump',
    ['--quiet', '--set', 'connection_strategy=lazy', '--scripts', 'addon.py'],
    {
      detached: true,
      windowsHide: true,
      env: {
        PYTHONUNBUFFERED: '1',
      },
    }
  );
  
  console.log(mitm.spawnargs);
  
  mitm.unref();
  mitm.on('exit', handleExit);
  mitm.stdout.on('data', handleStdOut);
  mitm.stderr.on('data', handleStdError);
  ```
  addon.py
  
  ```py
  class MyAddon:
    def running(self):
      print('running')
  
    def client_connected(self, client):
      print('client_connected')
  
  addons = [
    MyAddon()
  ]
  ```
  
  1. I'm on Ubuntu
  2. Adjust index.js to point to your local mitmproxy git venv
  3. Launch `node index.js` (Node 14 or 16 work both for me)
  4. Now open Chromium with mitmproxy configured. You don't need to enter any URL, Chromium will phone home anyway.
  5. Keep Chromium open and ctrl+c the node process
  6. Observe your fan getting louder and `top` showing mitmdump at 100% CPU
  
  https://user-images.githubusercontent.com/679144/124594746-740a7080-de60-11eb-9ffb-a5fc4b3ba24a.mp4
  
  #### System Information
  
  Happens with both v6 and HEAD.
  
  ```
  Mitmproxy: 7.0.0.dev (+492, commit af27556)
  Python:    3.8.10
  OpenSSL:   OpenSSL 1.1.1i  8 Dec 2020
  Platform:  Linux-5.8.0-59-generic-x86_64-with-glibc2.29
  ```
  
  
  </issue>
  <code>
  [start of mitmproxy/addons/termlog.py]
  1 from __future__ import annotations
  2 import asyncio
  3 import logging
  4 from typing import IO
  5 
  6 import sys
  7 
  8 from mitmproxy import ctx, log
  9 from mitmproxy.utils import vt_codes
  10 
  11 
  12 class TermLog:
  13     def __init__(
  14         self,
  15         out: IO[str] | None = None
  16     ):
  17         self.logger = TermLogHandler(out)
  18         self.logger.install()
  19 
  20     def load(self, loader):
  21         loader.add_option(
  22             "termlog_verbosity", str, "info", "Log verbosity.", choices=log.LogLevels
  23         )
  24         self.logger.setLevel(logging.INFO)
  25 
  26     def configure(self, updated):
  27         if "termlog_verbosity" in updated:
  28             self.logger.setLevel(ctx.options.termlog_verbosity.upper())
  29 
  30     def done(self):
  31         t = self._teardown()
  32         try:
  33             # try to delay teardown a bit.
  34             asyncio.create_task(t)
  35         except RuntimeError:
  36             # no event loop, we're in a test.
  37             asyncio.run(t)
  38 
  39     async def _teardown(self):
  40         self.logger.uninstall()
  41 
  42 
  43 class TermLogHandler(log.MitmLogHandler):
  44     def __init__(
  45         self,
  46         out: IO[str] | None = None
  47     ):
  48         super().__init__()
  49         self.file: IO[str] = out or sys.stdout
  50         self.has_vt_codes = vt_codes.ensure_supported(self.file)
  51         self.formatter = log.MitmFormatter(self.has_vt_codes)
  52 
  53     def emit(self, record: logging.LogRecord) -> None:
  54         print(
  55             self.format(record),
  56             file=self.file
  57         )
  58
  [end of mitmproxy/addons/termlog.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/mitmproxy/addons/termlog.py b/mitmproxy/addons/termlog.py
  --- a/mitmproxy/addons/termlog.py
  +++ b/mitmproxy/addons/termlog.py
  @@ -51,7 +51,9 @@
           self.formatter = log.MitmFormatter(self.has_vt_codes)
   
       def emit(self, record: logging.LogRecord) -> None:
  -        print(
  -            self.format(record),
  -            file=self.file
  -        )
  +        try:
  +            print(self.format(record), file=self.file)
  +        except OSError:
  +            # We cannot print, exit immediately.
  +            # See https://github.com/mitmproxy/mitmproxy/issues/4669
  +            sys.exit(1)
  
verification_info: {"golden_diff": "diff --git a/mitmproxy/addons/termlog.py b/mitmproxy/addons/termlog.py\n--- a/mitmproxy/addons/termlog.py\n+++ b/mitmproxy/addons/termlog.py\n@@ -51,7 +51,9 @@\n         self.formatter = log.MitmFormatter(self.has_vt_codes)\n \n     def emit(self, record: logging.LogRecord) -> None:\n-        print(\n-            self.format(record),\n-            file=self.file\n-        )\n+        try:\n+            print(self.format(record), file=self.file)\n+        except OSError:\n+            # We cannot print, exit immediately.\n+            # See https://github.com/mitmproxy/mitmproxy/issues/4669\n+            sys.exit(1)\n", "issue": "mitmdump jumps to 100% CPU when parent process exits\n#### Problem Description\r\n\r\nIt took me two days to make this reproduce in isolation. I hope someone with Python skills can figure out what is happening here. Depending on what the root cause is this might not even be related to my funny architecture.\r\n\r\nI'm spawning `mitmdump` from Node.js. If the node process exits mitmdump will be re-assigned to become a child of `systemd` (some unix wizardry). It will then immediately jump to 100% CPU and stay there. This _only_ happens when an addon is using at least one network event (go figure...). E.g. I'm using `client_connected` (works with `clientconnect` on v6 as well). If the addon is only using sth. like `running` the bug does not occur. Even better: if the addon originally only has \"running\" nothing bad happens. But if I then add a `client_connected` and save the file (and the addon is automatically reloaded) it will instantly jump to 100% CPU.\r\n\r\nMy guess is that it might be related to stdout and the switcheroo with the parent process? In my actual architecture the mitmdump process will poll the parent via gRPC every second and shutdown if it's gone. But the 100% CPU prevents that.\r\n\r\nUpdate: while trying to write down the exact steps it turns out this might only reproduce via local venv and and not if you download the binary. I'm not sure, it's confusing. I'm confused. But I have video proof, so I'm not completely insane.\r\n\r\n#### Steps to reproduce the behavior:\r\n\r\nindex.js\r\n\r\n```js\r\nconst path = require('path');\r\nconst { spawn } = require('child_process');\r\n\r\nfunction handleStdOut(data) {\r\n  console.log(`mitmdump stdout: ${data}`);\r\n}\r\n\r\nfunction handleStdError(data) {\r\n  console.error(`mitmdump stderr: ${data}`);\r\n}\r\n\r\nfunction handleExit(code) {\r\n  console.log(`mitm process exited with code ${code}`);\r\n}\r\n\r\nconst mitm = spawn(\r\n  // Adjust this path\r\n  '/home/alex/Projects/Bandsalat/src/forks/mitmproxy/venv/bin/mitmdump',\r\n  ['--quiet', '--set', 'connection_strategy=lazy', '--scripts', 'addon.py'],\r\n  {\r\n    detached: true,\r\n    windowsHide: true,\r\n    env: {\r\n      PYTHONUNBUFFERED: '1',\r\n    },\r\n  }\r\n);\r\n\r\nconsole.log(mitm.spawnargs);\r\n\r\nmitm.unref();\r\nmitm.on('exit', handleExit);\r\nmitm.stdout.on('data', handleStdOut);\r\nmitm.stderr.on('data', handleStdError);\r\n```\r\naddon.py\r\n\r\n```py\r\nclass MyAddon:\r\n  def running(self):\r\n    print('running')\r\n\r\n  def client_connected(self, client):\r\n    print('client_connected')\r\n\r\naddons = [\r\n  MyAddon()\r\n]\r\n```\r\n\r\n1. I'm on Ubuntu\r\n2. Adjust index.js to point to your local mitmproxy git venv\r\n3. Launch `node index.js` (Node 14 or 16 work both for me)\r\n4. Now open Chromium with mitmproxy configured. You don't need to enter any URL, Chromium will phone home anyway.\r\n5. Keep Chromium open and ctrl+c the node process\r\n6. Observe your fan getting louder and `top` showing mitmdump at 100% CPU\r\n\r\nhttps://user-images.githubusercontent.com/679144/124594746-740a7080-de60-11eb-9ffb-a5fc4b3ba24a.mp4\r\n\r\n#### System Information\r\n\r\nHappens with both v6 and HEAD.\r\n\r\n```\r\nMitmproxy: 7.0.0.dev (+492, commit af27556)\r\nPython:    3.8.10\r\nOpenSSL:   OpenSSL 1.1.1i  8 Dec 2020\r\nPlatform:  Linux-5.8.0-59-generic-x86_64-with-glibc2.29\r\n```\r\n\n", "before_files": [{"content": "from __future__ import annotations\nimport asyncio\nimport logging\nfrom typing import IO\n\nimport sys\n\nfrom mitmproxy import ctx, log\nfrom mitmproxy.utils import vt_codes\n\n\nclass TermLog:\n    def __init__(\n        self,\n        out: IO[str] | None = None\n    ):\n        self.logger = TermLogHandler(out)\n        self.logger.install()\n\n    def load(self, loader):\n        loader.add_option(\n            \"termlog_verbosity\", str, \"info\", \"Log verbosity.\", choices=log.LogLevels\n        )\n        self.logger.setLevel(logging.INFO)\n\n    def configure(self, updated):\n        if \"termlog_verbosity\" in updated:\n            self.logger.setLevel(ctx.options.termlog_verbosity.upper())\n\n    def done(self):\n        t = self._teardown()\n        try:\n            # try to delay teardown a bit.\n            asyncio.create_task(t)\n        except RuntimeError:\n            # no event loop, we're in a test.\n            asyncio.run(t)\n\n    async def _teardown(self):\n        self.logger.uninstall()\n\n\nclass TermLogHandler(log.MitmLogHandler):\n    def __init__(\n        self,\n        out: IO[str] | None = None\n    ):\n        super().__init__()\n        self.file: IO[str] = out or sys.stdout\n        self.has_vt_codes = vt_codes.ensure_supported(self.file)\n        self.formatter = log.MitmFormatter(self.has_vt_codes)\n\n    def emit(self, record: logging.LogRecord) -> None:\n        print(\n            self.format(record),\n            file=self.file\n        )\n", "path": "mitmproxy/addons/termlog.py"}]}