您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

container.py 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. #
  2. # Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import asyncio
  17. import contextlib
  18. import os
  19. import time
  20. from queue import Empty, Queue
  21. from threading import Lock
  22. from models.enums import SupportLanguage
  23. from util import env_setting_enabled, is_valid_memory_limit
  24. from utils.common import async_run_command
  25. from core.logger import logger
  26. _CONTAINER_QUEUES: dict[SupportLanguage, Queue] = {}
  27. _CONTAINER_LOCK: Lock = Lock()
  28. async def init_containers(size: int) -> tuple[int, int]:
  29. global _CONTAINER_QUEUES
  30. _CONTAINER_QUEUES = {SupportLanguage.PYTHON: Queue(), SupportLanguage.NODEJS: Queue()}
  31. with _CONTAINER_LOCK:
  32. while not _CONTAINER_QUEUES[SupportLanguage.PYTHON].empty():
  33. _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait()
  34. while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty():
  35. _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait()
  36. create_tasks = []
  37. for i in range(size):
  38. name = f"sandbox_python_{i}"
  39. logger.info(f"🛠️ Creating Python container {i + 1}/{size}")
  40. create_tasks.append(_prepare_container(name, SupportLanguage.PYTHON))
  41. name = f"sandbox_nodejs_{i}"
  42. logger.info(f"🛠️ Creating Node.js container {i + 1}/{size}")
  43. create_tasks.append(_prepare_container(name, SupportLanguage.NODEJS))
  44. results = await asyncio.gather(*create_tasks, return_exceptions=True)
  45. success_count = sum(1 for r in results if r is True)
  46. total_task_count = len(create_tasks)
  47. return success_count, total_task_count
  48. async def teardown_containers():
  49. with _CONTAINER_LOCK:
  50. while not _CONTAINER_QUEUES[SupportLanguage.PYTHON].empty():
  51. name = _CONTAINER_QUEUES[SupportLanguage.PYTHON].get_nowait()
  52. await async_run_command("docker", "rm", "-f", name, timeout=5)
  53. while not _CONTAINER_QUEUES[SupportLanguage.NODEJS].empty():
  54. name = _CONTAINER_QUEUES[SupportLanguage.NODEJS].get_nowait()
  55. await async_run_command("docker", "rm", "-f", name, timeout=5)
  56. async def _prepare_container(name: str, language: SupportLanguage) -> bool:
  57. """Prepare a single container"""
  58. with contextlib.suppress(Exception):
  59. await async_run_command("docker", "rm", "-f", name, timeout=5)
  60. if await create_container(name, language):
  61. _CONTAINER_QUEUES[language].put(name)
  62. return True
  63. return False
  64. async def create_container(name: str, language: SupportLanguage) -> bool:
  65. """Asynchronously create a container"""
  66. create_args = [
  67. "docker",
  68. "run",
  69. "-d",
  70. "--runtime=runsc",
  71. "--name",
  72. name,
  73. "--read-only",
  74. "--tmpfs",
  75. "/workspace:rw,exec,size=100M,uid=65534,gid=65534",
  76. "--tmpfs",
  77. "/tmp:rw,exec,size=50M",
  78. "--user",
  79. "nobody",
  80. "--workdir",
  81. "/workspace",
  82. ]
  83. if os.getenv("SANDBOX_MAX_MEMORY"):
  84. memory_limit = os.getenv("SANDBOX_MAX_MEMORY") or "256m"
  85. if is_valid_memory_limit(memory_limit):
  86. logger.info(f"SANDBOX_MAX_MEMORY: {os.getenv('SANDBOX_MAX_MEMORY')}")
  87. else:
  88. logger.info("Invalid SANDBOX_MAX_MEMORY, using default value: 256m")
  89. memory_limit = "256m"
  90. create_args.extend(["--memory", memory_limit])
  91. else:
  92. logger.info("Set default SANDBOX_MAX_MEMORY: 256m")
  93. create_args.extend(["--memory", "256m"])
  94. if env_setting_enabled("SANDBOX_ENABLE_SECCOMP", "false"):
  95. logger.info(f"SANDBOX_ENABLE_SECCOMP: {os.getenv('SANDBOX_ENABLE_SECCOMP')}")
  96. create_args.extend(["--security-opt", "seccomp=/app/seccomp-profile-default.json"])
  97. if language == SupportLanguage.PYTHON:
  98. create_args.append(os.getenv("SANDBOX_BASE_PYTHON_IMAGE", "sandbox-base-python:latest"))
  99. elif language == SupportLanguage.NODEJS:
  100. create_args.append(os.getenv("SANDBOX_BASE_NODEJS_IMAGE", "sandbox-base-nodejs:latest"))
  101. logger.info(f"Sandbox config:\n\t {create_args}")
  102. try:
  103. returncode, _, stderr = await async_run_command(*create_args, timeout=10)
  104. if returncode != 0:
  105. logger.error(f"❌ Container creation failed {name}: {stderr}")
  106. return False
  107. if language == SupportLanguage.NODEJS:
  108. copy_cmd = ["docker", "exec", name, "bash", "-c", "cp -a /app/node_modules /workspace/"]
  109. returncode, _, stderr = await async_run_command(*copy_cmd, timeout=10)
  110. if returncode != 0:
  111. logger.error(f"❌ Failed to prepare dependencies for {name}: {stderr}")
  112. return False
  113. return await container_is_running(name)
  114. except Exception as e:
  115. logger.error(f"❌ Container creation exception {name}: {str(e)}")
  116. return False
  117. async def recreate_container(name: str, language: SupportLanguage) -> bool:
  118. """Asynchronously recreate a container"""
  119. logger.info(f"🛠️ Recreating container: {name}")
  120. try:
  121. await async_run_command("docker", "rm", "-f", name, timeout=5)
  122. return await create_container(name, language)
  123. except Exception as e:
  124. logger.error(f"❌ Container {name} recreation failed: {str(e)}")
  125. return False
  126. async def release_container(name: str, language: SupportLanguage):
  127. """Asynchronously release a container"""
  128. with _CONTAINER_LOCK:
  129. if await container_is_running(name):
  130. _CONTAINER_QUEUES[language].put(name)
  131. logger.info(f"🟢 Released container: {name} (remaining available: {_CONTAINER_QUEUES[language].qsize()})")
  132. else:
  133. logger.warning(f"⚠️ Container {name} has crashed, attempting to recreate...")
  134. if await recreate_container(name, language):
  135. _CONTAINER_QUEUES[language].put(name)
  136. logger.info(f"✅ Container {name} successfully recreated and returned to queue")
  137. async def allocate_container_blocking(language: SupportLanguage, timeout=10) -> str:
  138. """Asynchronously allocate an available container"""
  139. start_time = time.time()
  140. while time.time() - start_time < timeout:
  141. try:
  142. name = _CONTAINER_QUEUES[language].get_nowait()
  143. with _CONTAINER_LOCK:
  144. if not await container_is_running(name) and not await recreate_container(name, language):
  145. continue
  146. return name
  147. except Empty:
  148. await asyncio.sleep(0.1)
  149. return ""
  150. async def container_is_running(name: str) -> bool:
  151. """Asynchronously check the container status"""
  152. try:
  153. returncode, stdout, _ = await async_run_command("docker", "inspect", "-f", "{{.State.Running}}", name, timeout=2)
  154. return returncode == 0 and stdout.strip() == "true"
  155. except Exception:
  156. return False