prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Task logs don't end
  ### Describe the bug
  
  Task logs contain the normal server logs, even though the task has stopped. This leads to duplicated logs in all created files.
  
  ### Steps to reproduce
  
  1. `cobbler mkloaders` (or any other task)
  2. Repeat step 1
  3. See logs being appended to completed tasks
  
  ### Expected behavior
  
  The files should only contain the logs for the task.
  
  ### Cobbler version
  
  Commit: 2a5c7ce9c1533bbf6f6b9050198d98aca45a06ae
  
  ````paste below
  Cobbler 3.4.0
    source: ?, ?
    build time: Fri Oct 14 14:17:09 2022
  ````
  
  ### Operating system
  
  ```
  e62e937a2de9:/var/log/cobbler/tasks # cat /etc/os-release 
  NAME="openSUSE Leap"
  VERSION="15.3"
  ID="opensuse-leap"
  ID_LIKE="suse opensuse"
  VERSION_ID="15.3"
  PRETTY_NAME="openSUSE Leap 15.3"
  ANSI_COLOR="0;32"
  CPE_NAME="cpe:/o:opensuse:leap:15.3"
  BUG_REPORT_URL="https://bugs.opensuse.org"
  HOME_URL="https://www.opensuse.org/"
  ```
  
  ### Cobbler log
  
  Not relevant
  
  ### Screenshots
  
  Not relevant
  
  ### Additional information
  
  Found by accident during another debugging session
  
  
  </issue>
  <code>
  [start of cobbler/utils/thread.py]
  1 """
  2 This module is responsible for managing the custom common threading logic Cobbler has.
  3 """
  4 
  5 import logging
  6 import pathlib
  7 from threading import Thread
  8 from typing import Callable
  9 
  10 from cobbler import enums
  11 from cobbler import utils
  12 
  13 
  14 class CobblerThread(Thread):
  15     """
  16     This is a custom thread that has a custom logger as well as logic to execute Cobbler triggers.
  17     """
  18 
  19     def __init__(
  20         self,
  21         event_id: str,
  22         remote,
  23         options: dict,
  24         task_name: str,
  25         api,
  26         run: Callable,
  27         on_done: Callable = None,
  28     ):
  29         """
  30         This constructor creates a Cobbler thread which then may be run by calling ``run()``.
  31 
  32         :param event_id: The event-id which is associated with this thread. Also used as thread name
  33         :param remote: The Cobbler remote object to execute actions with.
  34         :param options: Additional options which can be passed into the Thread.
  35         :param task_name: The high level task name which is used to trigger pre- and post-task triggers
  36         :param api: The Cobbler api object to resolve information with.
  37         :param run: The callable that is going to be executed with this thread.
  38         :param on_done: An optional callable that is going to be executed after ``run`` but before the triggers.
  39         """
  40         super().__init__(name=event_id)
  41         self.event_id = event_id
  42         self.remote = remote
  43         self.logger = logging.getLogger()
  44         self.__setup_logger()
  45         self._run = run
  46         self.on_done = on_done
  47         if options is None:
  48             options = {}
  49         self.options = options
  50         self.task_name = task_name
  51         self.api = api
  52 
  53     def __setup_logger(self):
  54         """
  55         Utility function that will set up the Python logger for the tasks in a special directory.
  56         """
  57         filename = pathlib.Path("/var/log/cobbler/tasks") / f"{self.event_id}.log"
  58         task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
  59         task_log_formatter = logging.Formatter(
  60             "[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s"
  61         )
  62         task_log_handler.setFormatter(task_log_formatter)
  63         self.logger.setLevel(logging.INFO)
  64         self.logger.addHandler(task_log_handler)
  65 
  66     def _set_task_state(self, new_state: enums.EventStatus):
  67         """
  68         Set the state of the task. (For internal use only)
  69 
  70         :param new_state: The new state of the task.
  71         """
  72         if not isinstance(new_state, enums.EventStatus):
  73             raise TypeError('"new_state" needs to be of type enums.EventStatus!')
  74         if self.event_id not in self.remote.events:
  75             raise ValueError('"event_id" not existing!')
  76         self.remote.events[self.event_id].state = new_state
  77         # clear the list of who has read it
  78         self.remote.events[self.event_id].read_by_who = []
  79         if new_state == enums.EventStatus.COMPLETE:
  80             self.logger.info("### TASK COMPLETE ###")
  81         elif new_state == enums.EventStatus.FAILED:
  82             self.logger.error("### TASK FAILED ###")
  83 
  84     def run(self):
  85         """
  86         Run the thread.
  87 
  88         :return: The return code of the action. This may a boolean or a Linux return code.
  89         """
  90         self.logger.info("start_task(%s); event_id(%s)", self.task_name, self.event_id)
  91         try:
  92             if utils.run_triggers(
  93                 api=self.api,
  94                 globber=f"/var/lib/cobbler/triggers/task/{self.task_name}/pre/*",
  95                 additional=self.options,
  96             ):
  97                 self._set_task_state(enums.EventStatus.FAILED)
  98                 return False
  99             rc = self._run(self)
  100             if rc is not None and not rc:
  101                 self._set_task_state(enums.EventStatus.FAILED)
  102             else:
  103                 self._set_task_state(enums.EventStatus.COMPLETE)
  104                 if self.on_done is not None:
  105                     self.on_done()
  106                 utils.run_triggers(
  107                     api=self.api,
  108                     globber=f"/var/lib/cobbler/triggers/task/{self.task_name}/post/*",
  109                     additional=self.options,
  110                 )
  111             return rc
  112         except Exception:
  113             utils.log_exc()
  114             self._set_task_state(enums.EventStatus.FAILED)
  115             return False
  116
  [end of cobbler/utils/thread.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/cobbler/utils/thread.py b/cobbler/utils/thread.py
  --- a/cobbler/utils/thread.py
  +++ b/cobbler/utils/thread.py
  @@ -41,6 +41,7 @@
           self.event_id = event_id
           self.remote = remote
           self.logger = logging.getLogger()
  +        self.__task_log_handler = None
           self.__setup_logger()
           self._run = run
           self.on_done = on_done
  @@ -55,13 +56,13 @@
           Utility function that will set up the Python logger for the tasks in a special directory.
           """
           filename = pathlib.Path("/var/log/cobbler/tasks") / f"{self.event_id}.log"
  -        task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
  +        self.__task_log_handler = logging.FileHandler(str(filename), encoding="utf-8")
           task_log_formatter = logging.Formatter(
               "[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s"
           )
  -        task_log_handler.setFormatter(task_log_formatter)
  +        self.__task_log_handler.setFormatter(task_log_formatter)
           self.logger.setLevel(logging.INFO)
  -        self.logger.addHandler(task_log_handler)
  +        self.logger.addHandler(self.__task_log_handler)
   
       def _set_task_state(self, new_state: enums.EventStatus):
           """
  @@ -113,3 +114,5 @@
               utils.log_exc()
               self._set_task_state(enums.EventStatus.FAILED)
               return False
  +        finally:
  +            self.logger.removeHandler(self.__task_log_handler)
  
verification_info: {"golden_diff": "diff --git a/cobbler/utils/thread.py b/cobbler/utils/thread.py\n--- a/cobbler/utils/thread.py\n+++ b/cobbler/utils/thread.py\n@@ -41,6 +41,7 @@\n         self.event_id = event_id\n         self.remote = remote\n         self.logger = logging.getLogger()\n+        self.__task_log_handler = None\n         self.__setup_logger()\n         self._run = run\n         self.on_done = on_done\n@@ -55,13 +56,13 @@\n         Utility function that will set up the Python logger for the tasks in a special directory.\n         \"\"\"\n         filename = pathlib.Path(\"/var/log/cobbler/tasks\") / f\"{self.event_id}.log\"\n-        task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n+        self.__task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n         task_log_formatter = logging.Formatter(\n             \"[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s\"\n         )\n-        task_log_handler.setFormatter(task_log_formatter)\n+        self.__task_log_handler.setFormatter(task_log_formatter)\n         self.logger.setLevel(logging.INFO)\n-        self.logger.addHandler(task_log_handler)\n+        self.logger.addHandler(self.__task_log_handler)\n \n     def _set_task_state(self, new_state: enums.EventStatus):\n         \"\"\"\n@@ -113,3 +114,5 @@\n             utils.log_exc()\n             self._set_task_state(enums.EventStatus.FAILED)\n             return False\n+        finally:\n+            self.logger.removeHandler(self.__task_log_handler)\n", "issue": "Task logs don't end\n### Describe the bug\r\n\r\nTask logs contain the normal server logs, even though the task has stopped. This leads to duplicated logs in all created files.\r\n\r\n### Steps to reproduce\r\n\r\n1. `cobbler mkloaders` (or any other task)\r\n2. Repeat step 1\r\n3. See logs being appended to completed tasks\r\n\r\n### Expected behavior\r\n\r\nThe files should only contain the logs for the task.\r\n\r\n### Cobbler version\r\n\r\nCommit: 2a5c7ce9c1533bbf6f6b9050198d98aca45a06ae\r\n\r\n````paste below\r\nCobbler 3.4.0\r\n  source: ?, ?\r\n  build time: Fri Oct 14 14:17:09 2022\r\n````\r\n\r\n### Operating system\r\n\r\n```\r\ne62e937a2de9:/var/log/cobbler/tasks # cat /etc/os-release \r\nNAME=\"openSUSE Leap\"\r\nVERSION=\"15.3\"\r\nID=\"opensuse-leap\"\r\nID_LIKE=\"suse opensuse\"\r\nVERSION_ID=\"15.3\"\r\nPRETTY_NAME=\"openSUSE Leap 15.3\"\r\nANSI_COLOR=\"0;32\"\r\nCPE_NAME=\"cpe:/o:opensuse:leap:15.3\"\r\nBUG_REPORT_URL=\"https://bugs.opensuse.org\"\r\nHOME_URL=\"https://www.opensuse.org/\"\r\n```\r\n\r\n### Cobbler log\r\n\r\nNot relevant\r\n\r\n### Screenshots\r\n\r\nNot relevant\r\n\r\n### Additional information\r\n\r\nFound by accident during another debugging session\r\n\n", "before_files": [{"content": "\"\"\"\nThis module is responsible for managing the custom common threading logic Cobbler has.\n\"\"\"\n\nimport logging\nimport pathlib\nfrom threading import Thread\nfrom typing import Callable\n\nfrom cobbler import enums\nfrom cobbler import utils\n\n\nclass CobblerThread(Thread):\n    \"\"\"\n    This is a custom thread that has a custom logger as well as logic to execute Cobbler triggers.\n    \"\"\"\n\n    def __init__(\n        self,\n        event_id: str,\n        remote,\n        options: dict,\n        task_name: str,\n        api,\n        run: Callable,\n        on_done: Callable = None,\n    ):\n        \"\"\"\n        This constructor creates a Cobbler thread which then may be run by calling ``run()``.\n\n        :param event_id: The event-id which is associated with this thread. Also used as thread name\n        :param remote: The Cobbler remote object to execute actions with.\n        :param options: Additional options which can be passed into the Thread.\n        :param task_name: The high level task name which is used to trigger pre- and post-task triggers\n        :param api: The Cobbler api object to resolve information with.\n        :param run: The callable that is going to be executed with this thread.\n        :param on_done: An optional callable that is going to be executed after ``run`` but before the triggers.\n        \"\"\"\n        super().__init__(name=event_id)\n        self.event_id = event_id\n        self.remote = remote\n        self.logger = logging.getLogger()\n        self.__setup_logger()\n        self._run = run\n        self.on_done = on_done\n        if options is None:\n            options = {}\n        self.options = options\n        self.task_name = task_name\n        self.api = api\n\n    def __setup_logger(self):\n        \"\"\"\n        Utility function that will set up the Python logger for the tasks in a special directory.\n        \"\"\"\n        filename = pathlib.Path(\"/var/log/cobbler/tasks\") / f\"{self.event_id}.log\"\n        task_log_handler = logging.FileHandler(str(filename), encoding=\"utf-8\")\n        task_log_formatter = logging.Formatter(\n            \"[%(threadName)s] %(asctime)s - %(levelname)s | %(message)s\"\n        )\n        task_log_handler.setFormatter(task_log_formatter)\n        self.logger.setLevel(logging.INFO)\n        self.logger.addHandler(task_log_handler)\n\n    def _set_task_state(self, new_state: enums.EventStatus):\n        \"\"\"\n        Set the state of the task. (For internal use only)\n\n        :param new_state: The new state of the task.\n        \"\"\"\n        if not isinstance(new_state, enums.EventStatus):\n            raise TypeError('\"new_state\" needs to be of type enums.EventStatus!')\n        if self.event_id not in self.remote.events:\n            raise ValueError('\"event_id\" not existing!')\n        self.remote.events[self.event_id].state = new_state\n        # clear the list of who has read it\n        self.remote.events[self.event_id].read_by_who = []\n        if new_state == enums.EventStatus.COMPLETE:\n            self.logger.info(\"### TASK COMPLETE ###\")\n        elif new_state == enums.EventStatus.FAILED:\n            self.logger.error(\"### TASK FAILED ###\")\n\n    def run(self):\n        \"\"\"\n        Run the thread.\n\n        :return: The return code of the action. This may a boolean or a Linux return code.\n        \"\"\"\n        self.logger.info(\"start_task(%s); event_id(%s)\", self.task_name, self.event_id)\n        try:\n            if utils.run_triggers(\n                api=self.api,\n                globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/pre/*\",\n                additional=self.options,\n            ):\n                self._set_task_state(enums.EventStatus.FAILED)\n                return False\n            rc = self._run(self)\n            if rc is not None and not rc:\n                self._set_task_state(enums.EventStatus.FAILED)\n            else:\n                self._set_task_state(enums.EventStatus.COMPLETE)\n                if self.on_done is not None:\n                    self.on_done()\n                utils.run_triggers(\n                    api=self.api,\n                    globber=f\"/var/lib/cobbler/triggers/task/{self.task_name}/post/*\",\n                    additional=self.options,\n                )\n            return rc\n        except Exception:\n            utils.log_exc()\n            self._set_task_state(enums.EventStatus.FAILED)\n            return False\n", "path": "cobbler/utils/thread.py"}]}