From 7b85688ff74fab9a72093f0aa5d0c7c1412f3104 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Mon, 4 Aug 2025 04:50:52 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat(RepositoryRunService):=20=EC=A4=91?= =?UTF-8?q?=EC=A7=80=20=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=ED=8F=AC=ED=8A=B8=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EA=B0=80=EB=8A=A5=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EB=9F=AC=EB=8B=9D=EC=BB=A8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EB=84=88=20=EB=A9=88=EC=B6=A4=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/repository/PortRegistryRepository.java | 4 ++-- .../repository/service/RepositoryRunService.java | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java index f2b8c769..6c5eb9fd 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/repository/PortRegistryRepository.java @@ -9,8 +9,8 @@ public interface PortRegistryRepository extends JpaRepository { - Optional findFirstByStatusOrderByPortAsc(PortStatus status); - Optional findByRepository(Repository repository); Optional findFirstByStatus(PortStatus status); + Optional findByRepositoryId(Long repositoryId); + } diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java index 268811fe..64035437 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java @@ -196,6 +196,14 @@ public boolean stopRepository(Long repositoryId, Long userId) { RunningContainer container = containerOpt.get(); + // 포트 정보 해제 로직 추가 + Optional portRegistryOpt = portRegistryRepository.findByRepositoryId(repositoryId); + portRegistryOpt.ifPresent(portRegistry -> { + portRegistry.release(); + portRegistryRepository.save(portRegistry); + log.info("Released port {} for repository {}", portRegistry.getPort(), repositoryId); + }); + // 샌드박스 서버에 중지 요청 boolean success = sandboxService.stopContainer(container.getUuid()); From b11ea23e6a350ba968ad2e79f02e4a0bb5c16677 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Mon, 4 Aug 2025 05:22:43 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat(RepositoryRunService):=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=ED=95=9C=EC=A7=80=2010=EB=B6=84=EC=9D=B4=20=EB=84=98?= =?UTF-8?q?=EC=9C=BC=EB=A9=B4=20=EC=9E=90=EB=8F=99=20=EC=A0=95=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeepwebideBeApplication.java | 2 ++ .../service/RepositoryRunService.java | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/com/deepdirect/deepwebide_be/DeepwebideBeApplication.java b/src/main/java/com/deepdirect/deepwebide_be/DeepwebideBeApplication.java index 7d402f7a..e32d3dad 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/DeepwebideBeApplication.java +++ b/src/main/java/com/deepdirect/deepwebide_be/DeepwebideBeApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling +@Async public class DeepwebideBeApplication { public static void main(String[] args) { diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java index 64035437..ad5a9b57 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java @@ -6,6 +6,7 @@ import com.deepdirect.deepwebide_be.file.repository.FileNodeRepository; import com.deepdirect.deepwebide_be.global.exception.ErrorCode; import com.deepdirect.deepwebide_be.global.exception.GlobalException; +import com.deepdirect.deepwebide_be.member.domain.User; import com.deepdirect.deepwebide_be.repository.domain.*; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryExecuteResponse; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStatusResponse; @@ -21,6 +22,7 @@ import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpMethod; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.mock.web.MockMultipartFile; @@ -92,6 +94,9 @@ public RepositoryExecuteResponse executeRepository(Long repositoryId, Long userI // 8. 실행 중인 컨테이너 정보 저장 saveRunningContainer(repositoryId, uuid, "sandbox-" + uuid, port, framework, s3Url); + // **자동 중지 타이머 시작** + scheduleAutoStop(repositoryId, uuid, 10); + log.info("Repository execution completed - repositoryId: {}, uuid: {}, port: {}", repositoryId, uuid, port); return RepositoryExecuteResponse.builder() @@ -544,6 +549,29 @@ private String extractLogs(Map response) { return result.isEmpty() ? "로그가 없습니다." : result; } + @Async + public void scheduleAutoStop(Long repositoryId, String uuid, int timeoutMinutes) { + try { + log.info("컨테이너 {}에 대해 {}분 후 자동 중지 예약", uuid, timeoutMinutes); + Thread.sleep(timeoutMinutes * 60 * 1000L); // 10분 대기 + + // 여기서 상태 한번 더 체크 후 중지 + Optional containerOpt = runningContainerRepository.findByRepositoryId(repositoryId); + if (containerOpt.isPresent() && containerOpt.get().getStatus().equals("RUNNING")) { + log.info("10분 경과 - 컨테이너 {} 자동 중지 시도", uuid); + + Repository repository = repositoryRepository.findById(repositoryId) + .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); + + stopRepository(repositoryId, repository.getOwner().getId()); + } else { + log.info("컨테이너 {}는 이미 중지됨 - 자동 중지 스킵", uuid); + } + } catch (InterruptedException e) { + log.info("자동 중지 타이머 인터럽트 발생: {}", e.getMessage()); + } + } + private Map createContainerInfo(RunningContainer container, String actualStatus) { return Map.of( "uuid", container.getUuid(), From f1fab17f2ac05a0dcd1d5d481a3670ec79ca084c Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Mon, 4 Aug 2025 05:29:38 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(RepositoryRunService):=20null=20?= =?UTF-8?q?=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/RepositoryRunService.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java index ad5a9b57..a15eb5b8 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java @@ -459,9 +459,11 @@ public Map getRepositoryLogs(Long repositoryId, Long userId, int Optional containerOpt = runningContainerRepository.findByRepositoryId(repositoryId); + Integer portValue = containerOpt.map(RunningContainer::getPort).orElse(-1); + if (containerOpt.isEmpty()) { return Map.of( - "port", null, + "port", portValue, // -1로 반환 "logs", "실행 중인 컨테이너가 없습니다." ); } @@ -486,44 +488,52 @@ public Map getRepositoryLogs(Long repositoryId, Long userId, int runningContainerRepository.save(container); return Map.of( - "port", null, + "port", portValue, "logs", "컨테이너가 존재하지 않아 중지되었습니다." ); } // 정상 응답 - 포트와 로그만 반환 String logs = extractLogs(response); + if (logs == null) logs = ""; return Map.of( - "port", container.getPort(), + "port", portValue, "logs", logs ); } return Map.of( - "port", container.getPort(), + "port", portValue, "logs", "로그를 가져올 수 없습니다." ); } catch (Exception httpEx) { log.error("HTTP request failed - url: {}", url, httpEx); + String msg = httpEx.getMessage(); + if (msg == null) msg = "알 수 없는 오류"; + // HTTP 오류 시에도 컨테이너 상태 확인 - if (httpEx.getMessage().contains("404") || httpEx.getMessage().contains("Not Found")) { + if (msg.contains("404") || msg.contains("Not Found")) { container.stop(); runningContainerRepository.save(container); } return Map.of( - "port", container.getPort(), - "logs", "로그 조회 중 오류가 발생했습니다: " + httpEx.getMessage() + "port", portValue, + "logs", "로그 조회 중 오류가 발생했습니다: " + msg ); } } catch (Exception e) { log.error("Failed to get repository logs: {}", repositoryId, e); + + String msg = e.getMessage(); + if (msg == null) msg = "알 수 없는 오류"; + return Map.of( - "port", null, - "logs", "오류가 발생했습니다: " + e.getMessage() + "port", -1, + "logs", "오류가 발생했습니다: " + msg ); } } From 200caf120a53786edddd232f73c47fa3e8037891 Mon Sep 17 00:00:00 2001 From: projectmiluju Date: Mon, 4 Aug 2025 06:19:40 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat(AutoStopSchedulerService):=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20?= =?UTF-8?q?=EB=AC=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SchedulerConfig.java | 18 +++++++++ .../controller/RepositoryRunController.java | 7 ++-- .../service/AutoStopSchedulerService.java | 23 ++++++++++++ .../service/RepositoryRunService.java | 37 +++++-------------- 4 files changed, 54 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/deepdirect/deepwebide_be/global/config/SchedulerConfig.java create mode 100644 src/main/java/com/deepdirect/deepwebide_be/repository/service/AutoStopSchedulerService.java diff --git a/src/main/java/com/deepdirect/deepwebide_be/global/config/SchedulerConfig.java b/src/main/java/com/deepdirect/deepwebide_be/global/config/SchedulerConfig.java new file mode 100644 index 00000000..4eba77d7 --- /dev/null +++ b/src/main/java/com/deepdirect/deepwebide_be/global/config/SchedulerConfig.java @@ -0,0 +1,18 @@ +package com.deepdirect.deepwebide_be.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@Configuration +@EnableScheduling +public class SchedulerConfig { + @Bean + public ThreadPoolTaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(2); + scheduler.setThreadNamePrefix("auto-stop-scheduler-"); + return scheduler; + } +} diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java b/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java index 7f27f54e..bcf7ee0c 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/controller/RepositoryRunController.java @@ -1,13 +1,11 @@ package com.deepdirect.deepwebide_be.repository.controller; import com.deepdirect.deepwebide_be.global.dto.ApiResponseDto; -import com.deepdirect.deepwebide_be.global.exception.ErrorCode; -import com.deepdirect.deepwebide_be.global.exception.GlobalException; import com.deepdirect.deepwebide_be.global.security.CustomUserDetails; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryExecuteResponse; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStatusResponse; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStopResponse; -import com.deepdirect.deepwebide_be.repository.repository.RepositoryRepository; +import com.deepdirect.deepwebide_be.repository.service.AutoStopSchedulerService; import com.deepdirect.deepwebide_be.repository.service.RepositoryRunService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -30,7 +28,7 @@ public class RepositoryRunController { private final RepositoryRunService repositoryRunService; - private final RepositoryRepository repositoryRepository; + private final AutoStopSchedulerService autoStopSchedulerService; @PostMapping("/{repositoryId}/execute") @Operation(summary = "레포지토리 실행", description = "레포지토리를 실행하고 실행 결과를 반환합니다.") @@ -39,6 +37,7 @@ public ResponseEntity> executeReposito @PathVariable Long repositoryId ) { RepositoryExecuteResponse resp = repositoryRunService.executeRepository(repositoryId, userDetails.getId()); + autoStopSchedulerService.scheduleAutoStop(repositoryId, resp.getUuid(), 10); return ResponseEntity.ok(ApiResponseDto.of(200, "레포지토리 실행 요청 완료", resp)); } diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/AutoStopSchedulerService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/AutoStopSchedulerService.java new file mode 100644 index 00000000..00cb60e5 --- /dev/null +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/AutoStopSchedulerService.java @@ -0,0 +1,23 @@ +package com.deepdirect.deepwebide_be.repository.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.stereotype.Service; + +import java.util.Date; + + +@Service +@RequiredArgsConstructor +public class AutoStopSchedulerService { + private final RepositoryRunService repositoryRunService; + private final TaskScheduler taskScheduler; + + // 10분 후 자동 중지 예약 + public void scheduleAutoStop(Long repositoryId, String uuid, int minutes) { + taskScheduler.schedule( + () -> repositoryRunService.stopIfTimeout(repositoryId, uuid), + new Date(System.currentTimeMillis() + minutes * 60 * 1000L) + ); + } +} diff --git a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java index a15eb5b8..a649736c 100644 --- a/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java +++ b/src/main/java/com/deepdirect/deepwebide_be/repository/service/RepositoryRunService.java @@ -6,7 +6,6 @@ import com.deepdirect.deepwebide_be.file.repository.FileNodeRepository; import com.deepdirect.deepwebide_be.global.exception.ErrorCode; import com.deepdirect.deepwebide_be.global.exception.GlobalException; -import com.deepdirect.deepwebide_be.member.domain.User; import com.deepdirect.deepwebide_be.repository.domain.*; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryExecuteResponse; import com.deepdirect.deepwebide_be.repository.dto.response.RepositoryStatusResponse; @@ -21,13 +20,10 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpMethod; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.client.RestTemplate; -import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.io.*; @@ -94,9 +90,6 @@ public RepositoryExecuteResponse executeRepository(Long repositoryId, Long userI // 8. 실행 중인 컨테이너 정보 저장 saveRunningContainer(repositoryId, uuid, "sandbox-" + uuid, port, framework, s3Url); - // **자동 중지 타이머 시작** - scheduleAutoStop(repositoryId, uuid, 10); - log.info("Repository execution completed - repositoryId: {}, uuid: {}, port: {}", repositoryId, uuid, port); return RepositoryExecuteResponse.builder() @@ -559,26 +552,16 @@ private String extractLogs(Map response) { return result.isEmpty() ? "로그가 없습니다." : result; } - @Async - public void scheduleAutoStop(Long repositoryId, String uuid, int timeoutMinutes) { - try { - log.info("컨테이너 {}에 대해 {}분 후 자동 중지 예약", uuid, timeoutMinutes); - Thread.sleep(timeoutMinutes * 60 * 1000L); // 10분 대기 - - // 여기서 상태 한번 더 체크 후 중지 - Optional containerOpt = runningContainerRepository.findByRepositoryId(repositoryId); - if (containerOpt.isPresent() && containerOpt.get().getStatus().equals("RUNNING")) { - log.info("10분 경과 - 컨테이너 {} 자동 중지 시도", uuid); - - Repository repository = repositoryRepository.findById(repositoryId) - .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); - - stopRepository(repositoryId, repository.getOwner().getId()); - } else { - log.info("컨테이너 {}는 이미 중지됨 - 자동 중지 스킵", uuid); - } - } catch (InterruptedException e) { - log.info("자동 중지 타이머 인터럽트 발생: {}", e.getMessage()); + @Transactional + public void stopIfTimeout(Long repositoryId, String uuid) { + Optional containerOpt = runningContainerRepository.findByRepositoryId(repositoryId); + if (containerOpt.isPresent() && "RUNNING".equals(containerOpt.get().getStatus())) { + log.info("자동 중지 조건 충족: repositoryId={}, uuid={}", repositoryId, uuid); + Repository repo = repositoryRepository.findById(repositoryId) + .orElseThrow(() -> new GlobalException(ErrorCode.REPOSITORY_NOT_FOUND)); + stopRepository(repositoryId, repo.getOwner().getId()); + } else { + log.info("이미 중지된 컨테이너: repositoryId={}, uuid={}", repositoryId, uuid); } }