We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Docker fails to read results
The results of evaluations cannot be read with the latest version of docker

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/grandchallenge/container_exec/backends/docker.py`
Content:
```
1 import io
2 import json
3 import os
4 import tarfile
5 import uuid
6 from contextlib import contextmanager
7 from json import JSONDecodeError
8 from pathlib import Path
9 from random import randint
10 from time import sleep
11 from typing import Tuple
12 
13 import docker
14 from django.conf import settings
15 from django.core.files import File
16 from docker.api.container import ContainerApiMixin
17 from docker.errors import ContainerError, APIError
18 from docker.tls import TLSConfig
19 from requests import HTTPError
20 
21 
22 class Executor(object):
23     def __init__(
24         self,
25         *,
26         job_id: uuid.UUID,
27         input_files: Tuple[File, ...],
28         exec_image: File,
29         exec_image_sha256: str,
30         results_file: Path,
31     ):
32         super().__init__()
33         self._job_id = str(job_id)
34         self._input_files = input_files
35         self._exec_image = exec_image
36         self._exec_image_sha256 = exec_image_sha256
37         self._io_image = settings.CONTAINER_EXEC_IO_IMAGE
38         self._results_file = results_file
39 
40         client_kwargs = {"base_url": settings.CONTAINER_EXEC_DOCKER_BASE_URL}
41 
42         if settings.CONTAINER_EXEC_DOCKER_TLSVERIFY:
43             tlsconfig = TLSConfig(
44                 verify=True,
45                 client_cert=(
46                     settings.CONTAINER_EXEC_DOCKER_TLSCERT,
47                     settings.CONTAINER_EXEC_DOCKER_TLSKEY,
48                 ),
49                 ca_cert=settings.CONTAINER_EXEC_DOCKER_TLSCACERT,
50             )
51             client_kwargs.update({"tls": tlsconfig})
52 
53         self._client = docker.DockerClient(**client_kwargs)
54 
55         self._input_volume = f"{self._job_id}-input"
56         self._output_volume = f"{self._job_id}-output"
57 
58         self._run_kwargs = {
59             "labels": {"job_id": self._job_id},
60             "network_disabled": True,
61             "mem_limit": settings.CONTAINER_EXEC_MEMORY_LIMIT,
62             "cpu_period": settings.CONTAINER_EXEC_CPU_PERIOD,
63             "cpu_quota": settings.CONTAINER_EXEC_CPU_QUOTA,
64         }
65 
66     def __enter__(self):
67         return self
68 
69     def __exit__(self, exc_type, exc_val, exc_tb):
70         flt = {"label": f"job_id={self._job_id}"}
71 
72         for container in self._client.containers.list(filters=flt):
73             container.stop()
74 
75         self.__retry_docker_obj_prune(obj=self._client.containers, filters=flt)
76         self.__retry_docker_obj_prune(obj=self._client.volumes, filters=flt)
77 
78     @staticmethod
79     def __retry_docker_obj_prune(*, obj, filters: dict):
80         # Retry and exponential backoff of the prune command as only 1 prune
81         # operation can occur at a time on a docker host
82         num_retries = 0
83         e = APIError
84         while num_retries < 3:
85             try:
86                 obj.prune(filters=filters)
87                 break
88             except (APIError, HTTPError) as e:
89                 num_retries += 1
90                 sleep((2 ** num_retries) + (randint(0, 1000) / 1000))
91         else:
92             raise e
93 
94     def execute(self) -> dict:
95         self._pull_images()
96         self._create_io_volumes()
97         self._provision_input_volume()
98         self._chmod_output()
99         self._execute_container()
100         return self._get_result()
101 
102     def _pull_images(self):
103         self._client.images.pull(repository=self._io_image)
104 
105         if self._exec_image_sha256 not in [
106             img.id for img in self._client.images.list()
107         ]:
108             with self._exec_image.open("rb") as f:
109                 self._client.images.load(f)
110 
111     def _create_io_volumes(self):
112         for volume in [self._input_volume, self._output_volume]:
113             self._client.volumes.create(
114                 name=volume, labels=self._run_kwargs["labels"]
115             )
116 
117     def _provision_input_volume(self):
118         try:
119             with cleanup(
120                 self._client.containers.run(
121                     image=self._io_image,
122                     volumes={
123                         self._input_volume: {"bind": "/input/", "mode": "rw"}
124                     },
125                     detach=True,
126                     tty=True,
127                     **self._run_kwargs,
128                 )
129             ) as writer:
130                 self._copy_input_files(writer=writer)
131         except Exception as exc:
132             raise RuntimeError(str(exc))
133 
134     def _copy_input_files(self, writer):
135         for file in self._input_files:
136             put_file(
137                 container=writer,
138                 src=file,
139                 dest=f"/input/{Path(file.name).name}",
140             )
141 
142     def _chmod_output(self):
143         """ Ensure that the output is writable """
144         try:
145             self._client.containers.run(
146                 image=self._io_image,
147                 volumes={
148                     self._output_volume: {"bind": "/output/", "mode": "rw"}
149                 },
150                 command="chmod 777 /output/",
151                 remove=True,
152                 **self._run_kwargs,
153             )
154         except Exception as exc:
155             raise RuntimeError(str(exc))
156 
157     def _execute_container(self):
158         try:
159             self._client.containers.run(
160                 image=self._exec_image_sha256,
161                 volumes={
162                     self._input_volume: {"bind": "/input/", "mode": "rw"},
163                     self._output_volume: {"bind": "/output/", "mode": "rw"},
164                 },
165                 remove=True,
166                 **self._run_kwargs,
167             )
168         except ContainerError as exc:
169             raise RuntimeError(exc.stderr.decode())
170 
171     def _get_result(self) -> dict:
172         try:
173             result = self._client.containers.run(
174                 image=self._io_image,
175                 volumes={
176                     self._output_volume: {"bind": "/output/", "mode": "ro"}
177                 },
178                 command=f"cat {self._results_file}",
179                 remove=True,
180                 **self._run_kwargs,
181             )
182         except ContainerError as exc:
183             raise RuntimeError(exc.stderr.decode())
184 
185         try:
186             result = json.loads(
187                 result.decode(),
188                 parse_constant=lambda x: None,  # Removes -inf, inf and NaN
189             )
190         except JSONDecodeError as exc:
191             raise RuntimeError(exc.msg)
192 
193         return result
194 
195 
196 @contextmanager
197 def cleanup(container: ContainerApiMixin):
198     """
199     Cleans up a docker container which is running in detached mode
200 
201     :param container: An instance of a container
202     :return:
203     """
204     try:
205         yield container
206 
207     finally:
208         container.stop()
209         container.remove(force=True)
210 
211 
212 def put_file(*, container: ContainerApiMixin, src: File, dest: str) -> ():
213     """
214     Puts a file on the host into a container.
215     This method will create an in memory tar archive, add the src file to this
216     and upload it to the docker container where it will be unarchived at dest.
217 
218     :param container: The container to write to
219     :param src: The path to the source file on the host
220     :param dest: The path to the target file in the container
221     :return:
222     """
223     tar_b = io.BytesIO()
224 
225     tarinfo = tarfile.TarInfo(name=os.path.basename(dest))
226     tarinfo.size = src.size
227 
228     with tarfile.open(fileobj=tar_b, mode="w") as tar, src.open("rb") as f:
229         tar.addfile(tarinfo, fileobj=f)
230 
231     tar_b.seek(0)
232     container.put_archive(os.path.dirname(dest), tar_b)
233 
234 
235 def get_file(*, container: ContainerApiMixin, src: Path):
236     tarstrm, info = container.get_archive(src)
237 
238     if info["size"] > 2E9:
239         raise ValueError(f"File {src} is too big to be decompressed.")
240 
241     file_obj = io.BytesIO()
242     for ts in tarstrm:
243         file_obj.write(ts)
244 
245     file_obj.seek(0)
246     tar = tarfile.open(mode="r", fileobj=file_obj)
247     content = tar.extractfile(src.name)
248 
249     return content
250
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/app/grandchallenge/container_exec/backends/docker.py b/app/grandchallenge/container_exec/backends/docker.py
--- a/app/grandchallenge/container_exec/backends/docker.py
+++ b/app/grandchallenge/container_exec/backends/docker.py
@@ -169,22 +169,30 @@
             raise RuntimeError(exc.stderr.decode())
 
     def _get_result(self) -> dict:
+        """
+        Read and parse the created results file. Due to a bug in the docker
+        client, copy the file to memory first rather than cat and read
+        stdout.
+        """
         try:
-            result = self._client.containers.run(
-                image=self._io_image,
-                volumes={
-                    self._output_volume: {"bind": "/output/", "mode": "ro"}
-                },
-                command=f"cat {self._results_file}",
-                remove=True,
-                **self._run_kwargs,
-            )
-        except ContainerError as exc:
-            raise RuntimeError(exc.stderr.decode())
+            with cleanup(
+                self._client.containers.run(
+                    image=self._io_image,
+                    volumes={
+                        self._output_volume: {"bind": "/output/", "mode": "ro"}
+                    },
+                    detach=True,
+                    tty=True,
+                    **self._run_kwargs,
+                )
+            ) as reader:
+                result = get_file(container=reader, src=self._results_file)
+        except Exception as e:
+            raise RuntimeError(str(e))
 
         try:
             result = json.loads(
-                result.decode(),
+                result.read().decode(),
                 parse_constant=lambda x: None,  # Removes -inf, inf and NaN
             )
         except JSONDecodeError as exc:

{"golden_diff": "diff --git a/app/grandchallenge/container_exec/backends/docker.py b/app/grandchallenge/container_exec/backends/docker.py\n--- a/app/grandchallenge/container_exec/backends/docker.py\n+++ b/app/grandchallenge/container_exec/backends/docker.py\n@@ -169,22 +169,30 @@\n             raise RuntimeError(exc.stderr.decode())\n \n     def _get_result(self) -> dict:\n+        \"\"\"\n+        Read and parse the created results file. Due to a bug in the docker\n+        client, copy the file to memory first rather than cat and read\n+        stdout.\n+        \"\"\"\n         try:\n-            result = self._client.containers.run(\n-                image=self._io_image,\n-                volumes={\n-                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"ro\"}\n-                },\n-                command=f\"cat {self._results_file}\",\n-                remove=True,\n-                **self._run_kwargs,\n-            )\n-        except ContainerError as exc:\n-            raise RuntimeError(exc.stderr.decode())\n+            with cleanup(\n+                self._client.containers.run(\n+                    image=self._io_image,\n+                    volumes={\n+                        self._output_volume: {\"bind\": \"/output/\", \"mode\": \"ro\"}\n+                    },\n+                    detach=True,\n+                    tty=True,\n+                    **self._run_kwargs,\n+                )\n+            ) as reader:\n+                result = get_file(container=reader, src=self._results_file)\n+        except Exception as e:\n+            raise RuntimeError(str(e))\n \n         try:\n             result = json.loads(\n-                result.decode(),\n+                result.read().decode(),\n                 parse_constant=lambda x: None,  # Removes -inf, inf and NaN\n             )\n         except JSONDecodeError as exc:\n", "issue": "Docker fails to read results\nThe results of evaluations cannot be read with the latest version of docker\n", "before_files": [{"content": "import io\nimport json\nimport os\nimport tarfile\nimport uuid\nfrom contextlib import contextmanager\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom random import randint\nfrom time import sleep\nfrom typing import Tuple\n\nimport docker\nfrom django.conf import settings\nfrom django.core.files import File\nfrom docker.api.container import ContainerApiMixin\nfrom docker.errors import ContainerError, APIError\nfrom docker.tls import TLSConfig\nfrom requests import HTTPError\n\n\nclass Executor(object):\n    def __init__(\n        self,\n        *,\n        job_id: uuid.UUID,\n        input_files: Tuple[File, ...],\n        exec_image: File,\n        exec_image_sha256: str,\n        results_file: Path,\n    ):\n        super().__init__()\n        self._job_id = str(job_id)\n        self._input_files = input_files\n        self._exec_image = exec_image\n        self._exec_image_sha256 = exec_image_sha256\n        self._io_image = settings.CONTAINER_EXEC_IO_IMAGE\n        self._results_file = results_file\n\n        client_kwargs = {\"base_url\": settings.CONTAINER_EXEC_DOCKER_BASE_URL}\n\n        if settings.CONTAINER_EXEC_DOCKER_TLSVERIFY:\n            tlsconfig = TLSConfig(\n                verify=True,\n                client_cert=(\n                    settings.CONTAINER_EXEC_DOCKER_TLSCERT,\n                    settings.CONTAINER_EXEC_DOCKER_TLSKEY,\n                ),\n                ca_cert=settings.CONTAINER_EXEC_DOCKER_TLSCACERT,\n            )\n            client_kwargs.update({\"tls\": tlsconfig})\n\n        self._client = docker.DockerClient(**client_kwargs)\n\n        self._input_volume = f\"{self._job_id}-input\"\n        self._output_volume = f\"{self._job_id}-output\"\n\n        self._run_kwargs = {\n            \"labels\": {\"job_id\": self._job_id},\n            \"network_disabled\": True,\n            \"mem_limit\": settings.CONTAINER_EXEC_MEMORY_LIMIT,\n            \"cpu_period\": settings.CONTAINER_EXEC_CPU_PERIOD,\n            \"cpu_quota\": settings.CONTAINER_EXEC_CPU_QUOTA,\n        }\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        flt = {\"label\": f\"job_id={self._job_id}\"}\n\n        for container in self._client.containers.list(filters=flt):\n            container.stop()\n\n        self.__retry_docker_obj_prune(obj=self._client.containers, filters=flt)\n        self.__retry_docker_obj_prune(obj=self._client.volumes, filters=flt)\n\n    @staticmethod\n    def __retry_docker_obj_prune(*, obj, filters: dict):\n        # Retry and exponential backoff of the prune command as only 1 prune\n        # operation can occur at a time on a docker host\n        num_retries = 0\n        e = APIError\n        while num_retries < 3:\n            try:\n                obj.prune(filters=filters)\n                break\n            except (APIError, HTTPError) as e:\n                num_retries += 1\n                sleep((2 ** num_retries) + (randint(0, 1000) / 1000))\n        else:\n            raise e\n\n    def execute(self) -> dict:\n        self._pull_images()\n        self._create_io_volumes()\n        self._provision_input_volume()\n        self._chmod_output()\n        self._execute_container()\n        return self._get_result()\n\n    def _pull_images(self):\n        self._client.images.pull(repository=self._io_image)\n\n        if self._exec_image_sha256 not in [\n            img.id for img in self._client.images.list()\n        ]:\n            with self._exec_image.open(\"rb\") as f:\n                self._client.images.load(f)\n\n    def _create_io_volumes(self):\n        for volume in [self._input_volume, self._output_volume]:\n            self._client.volumes.create(\n                name=volume, labels=self._run_kwargs[\"labels\"]\n            )\n\n    def _provision_input_volume(self):\n        try:\n            with cleanup(\n                self._client.containers.run(\n                    image=self._io_image,\n                    volumes={\n                        self._input_volume: {\"bind\": \"/input/\", \"mode\": \"rw\"}\n                    },\n                    detach=True,\n                    tty=True,\n                    **self._run_kwargs,\n                )\n            ) as writer:\n                self._copy_input_files(writer=writer)\n        except Exception as exc:\n            raise RuntimeError(str(exc))\n\n    def _copy_input_files(self, writer):\n        for file in self._input_files:\n            put_file(\n                container=writer,\n                src=file,\n                dest=f\"/input/{Path(file.name).name}\",\n            )\n\n    def _chmod_output(self):\n        \"\"\" Ensure that the output is writable \"\"\"\n        try:\n            self._client.containers.run(\n                image=self._io_image,\n                volumes={\n                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"rw\"}\n                },\n                command=\"chmod 777 /output/\",\n                remove=True,\n                **self._run_kwargs,\n            )\n        except Exception as exc:\n            raise RuntimeError(str(exc))\n\n    def _execute_container(self):\n        try:\n            self._client.containers.run(\n                image=self._exec_image_sha256,\n                volumes={\n                    self._input_volume: {\"bind\": \"/input/\", \"mode\": \"rw\"},\n                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"rw\"},\n                },\n                remove=True,\n                **self._run_kwargs,\n            )\n        except ContainerError as exc:\n            raise RuntimeError(exc.stderr.decode())\n\n    def _get_result(self) -> dict:\n        try:\n            result = self._client.containers.run(\n                image=self._io_image,\n                volumes={\n                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"ro\"}\n                },\n                command=f\"cat {self._results_file}\",\n                remove=True,\n                **self._run_kwargs,\n            )\n        except ContainerError as exc:\n            raise RuntimeError(exc.stderr.decode())\n\n        try:\n            result = json.loads(\n                result.decode(),\n                parse_constant=lambda x: None,  # Removes -inf, inf and NaN\n            )\n        except JSONDecodeError as exc:\n            raise RuntimeError(exc.msg)\n\n        return result\n\n\n@contextmanager\ndef cleanup(container: ContainerApiMixin):\n    \"\"\"\n    Cleans up a docker container which is running in detached mode\n\n    :param container: An instance of a container\n    :return:\n    \"\"\"\n    try:\n        yield container\n\n    finally:\n        container.stop()\n        container.remove(force=True)\n\n\ndef put_file(*, container: ContainerApiMixin, src: File, dest: str) -> ():\n    \"\"\"\n    Puts a file on the host into a container.\n    This method will create an in memory tar archive, add the src file to this\n    and upload it to the docker container where it will be unarchived at dest.\n\n    :param container: The container to write to\n    :param src: The path to the source file on the host\n    :param dest: The path to the target file in the container\n    :return:\n    \"\"\"\n    tar_b = io.BytesIO()\n\n    tarinfo = tarfile.TarInfo(name=os.path.basename(dest))\n    tarinfo.size = src.size\n\n    with tarfile.open(fileobj=tar_b, mode=\"w\") as tar, src.open(\"rb\") as f:\n        tar.addfile(tarinfo, fileobj=f)\n\n    tar_b.seek(0)\n    container.put_archive(os.path.dirname(dest), tar_b)\n\n\ndef get_file(*, container: ContainerApiMixin, src: Path):\n    tarstrm, info = container.get_archive(src)\n\n    if info[\"size\"] > 2E9:\n        raise ValueError(f\"File {src} is too big to be decompressed.\")\n\n    file_obj = io.BytesIO()\n    for ts in tarstrm:\n        file_obj.write(ts)\n\n    file_obj.seek(0)\n    tar = tarfile.open(mode=\"r\", fileobj=file_obj)\n    content = tar.extractfile(src.name)\n\n    return content\n", "path": "app/grandchallenge/container_exec/backends/docker.py"}], "after_files": [{"content": "import io\nimport json\nimport os\nimport tarfile\nimport uuid\nfrom contextlib import contextmanager\nfrom json import JSONDecodeError\nfrom pathlib import Path\nfrom random import randint\nfrom time import sleep\nfrom typing import Tuple\n\nimport docker\nfrom django.conf import settings\nfrom django.core.files import File\nfrom docker.api.container import ContainerApiMixin\nfrom docker.errors import ContainerError, APIError\nfrom docker.tls import TLSConfig\nfrom requests import HTTPError\n\n\nclass Executor(object):\n    def __init__(\n        self,\n        *,\n        job_id: uuid.UUID,\n        input_files: Tuple[File, ...],\n        exec_image: File,\n        exec_image_sha256: str,\n        results_file: Path,\n    ):\n        super().__init__()\n        self._job_id = str(job_id)\n        self._input_files = input_files\n        self._exec_image = exec_image\n        self._exec_image_sha256 = exec_image_sha256\n        self._io_image = settings.CONTAINER_EXEC_IO_IMAGE\n        self._results_file = results_file\n\n        client_kwargs = {\"base_url\": settings.CONTAINER_EXEC_DOCKER_BASE_URL}\n\n        if settings.CONTAINER_EXEC_DOCKER_TLSVERIFY:\n            tlsconfig = TLSConfig(\n                verify=True,\n                client_cert=(\n                    settings.CONTAINER_EXEC_DOCKER_TLSCERT,\n                    settings.CONTAINER_EXEC_DOCKER_TLSKEY,\n                ),\n                ca_cert=settings.CONTAINER_EXEC_DOCKER_TLSCACERT,\n            )\n            client_kwargs.update({\"tls\": tlsconfig})\n\n        self._client = docker.DockerClient(**client_kwargs)\n\n        self._input_volume = f\"{self._job_id}-input\"\n        self._output_volume = f\"{self._job_id}-output\"\n\n        self._run_kwargs = {\n            \"labels\": {\"job_id\": self._job_id},\n            \"network_disabled\": True,\n            \"mem_limit\": settings.CONTAINER_EXEC_MEMORY_LIMIT,\n            \"cpu_period\": settings.CONTAINER_EXEC_CPU_PERIOD,\n            \"cpu_quota\": settings.CONTAINER_EXEC_CPU_QUOTA,\n        }\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        flt = {\"label\": f\"job_id={self._job_id}\"}\n\n        for container in self._client.containers.list(filters=flt):\n            container.stop()\n\n        self.__retry_docker_obj_prune(obj=self._client.containers, filters=flt)\n        self.__retry_docker_obj_prune(obj=self._client.volumes, filters=flt)\n\n    @staticmethod\n    def __retry_docker_obj_prune(*, obj, filters: dict):\n        # Retry and exponential backoff of the prune command as only 1 prune\n        # operation can occur at a time on a docker host\n        num_retries = 0\n        e = APIError\n        while num_retries < 3:\n            try:\n                obj.prune(filters=filters)\n                break\n            except (APIError, HTTPError) as e:\n                num_retries += 1\n                sleep((2 ** num_retries) + (randint(0, 1000) / 1000))\n        else:\n            raise e\n\n    def execute(self) -> dict:\n        self._pull_images()\n        self._create_io_volumes()\n        self._provision_input_volume()\n        self._chmod_output()\n        self._execute_container()\n        return self._get_result()\n\n    def _pull_images(self):\n        self._client.images.pull(repository=self._io_image)\n\n        if self._exec_image_sha256 not in [\n            img.id for img in self._client.images.list()\n        ]:\n            with self._exec_image.open(\"rb\") as f:\n                self._client.images.load(f)\n\n    def _create_io_volumes(self):\n        for volume in [self._input_volume, self._output_volume]:\n            self._client.volumes.create(\n                name=volume, labels=self._run_kwargs[\"labels\"]\n            )\n\n    def _provision_input_volume(self):\n        try:\n            with cleanup(\n                self._client.containers.run(\n                    image=self._io_image,\n                    volumes={\n                        self._input_volume: {\"bind\": \"/input/\", \"mode\": \"rw\"}\n                    },\n                    detach=True,\n                    tty=True,\n                    **self._run_kwargs,\n                )\n            ) as writer:\n                self._copy_input_files(writer=writer)\n        except Exception as exc:\n            raise RuntimeError(str(exc))\n\n    def _copy_input_files(self, writer):\n        for file in self._input_files:\n            put_file(\n                container=writer,\n                src=file,\n                dest=f\"/input/{Path(file.name).name}\",\n            )\n\n    def _chmod_output(self):\n        \"\"\" Ensure that the output is writable \"\"\"\n        try:\n            self._client.containers.run(\n                image=self._io_image,\n                volumes={\n                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"rw\"}\n                },\n                command=\"chmod 777 /output/\",\n                remove=True,\n                **self._run_kwargs,\n            )\n        except Exception as exc:\n            raise RuntimeError(str(exc))\n\n    def _execute_container(self):\n        try:\n            self._client.containers.run(\n                image=self._exec_image_sha256,\n                volumes={\n                    self._input_volume: {\"bind\": \"/input/\", \"mode\": \"rw\"},\n                    self._output_volume: {\"bind\": \"/output/\", \"mode\": \"rw\"},\n                },\n                remove=True,\n                **self._run_kwargs,\n            )\n        except ContainerError as exc:\n            raise RuntimeError(exc.stderr.decode())\n\n    def _get_result(self) -> dict:\n        \"\"\"\n        Read and parse the created results file. Due to a bug in the docker\n        client, copy the file to memory first rather than cat and read\n        stdout.\n        \"\"\"\n        try:\n            with cleanup(\n                self._client.containers.run(\n                    image=self._io_image,\n                    volumes={\n                        self._output_volume: {\"bind\": \"/output/\", \"mode\": \"ro\"}\n                    },\n                    detach=True,\n                    tty=True,\n                    **self._run_kwargs,\n                )\n            ) as reader:\n                result = get_file(container=reader, src=self._results_file)\n        except Exception as e:\n            raise RuntimeError(str(e))\n\n        try:\n            result = json.loads(\n                result.read().decode(),\n                parse_constant=lambda x: None,  # Removes -inf, inf and NaN\n            )\n        except JSONDecodeError as exc:\n            raise RuntimeError(exc.msg)\n\n        return result\n\n\n@contextmanager\ndef cleanup(container: ContainerApiMixin):\n    \"\"\"\n    Cleans up a docker container which is running in detached mode\n\n    :param container: An instance of a container\n    :return:\n    \"\"\"\n    try:\n        yield container\n\n    finally:\n        container.stop()\n        container.remove(force=True)\n\n\ndef put_file(*, container: ContainerApiMixin, src: File, dest: str) -> ():\n    \"\"\"\n    Puts a file on the host into a container.\n    This method will create an in memory tar archive, add the src file to this\n    and upload it to the docker container where it will be unarchived at dest.\n\n    :param container: The container to write to\n    :param src: The path to the source file on the host\n    :param dest: The path to the target file in the container\n    :return:\n    \"\"\"\n    tar_b = io.BytesIO()\n\n    tarinfo = tarfile.TarInfo(name=os.path.basename(dest))\n    tarinfo.size = src.size\n\n    with tarfile.open(fileobj=tar_b, mode=\"w\") as tar, src.open(\"rb\") as f:\n        tar.addfile(tarinfo, fileobj=f)\n\n    tar_b.seek(0)\n    container.put_archive(os.path.dirname(dest), tar_b)\n\n\ndef get_file(*, container: ContainerApiMixin, src: Path):\n    tarstrm, info = container.get_archive(src)\n\n    if info[\"size\"] > 2E9:\n        raise ValueError(f\"File {src} is too big to be decompressed.\")\n\n    file_obj = io.BytesIO()\n    for ts in tarstrm:\n        file_obj.write(ts)\n\n    file_obj.seek(0)\n    tar = tarfile.open(mode=\"r\", fileobj=file_obj)\n    content = tar.extractfile(src.name)\n\n    return content\n", "path": "app/grandchallenge/container_exec/backends/docker.py"}]}