From 96540689c2600bc68959b77709fbfcf0fc2dd307 Mon Sep 17 00:00:00 2001 From: pabloFuente Date: Mon, 8 Oct 2018 17:04:26 +0200 Subject: [PATCH] openvidu-server: gracefully stop recording of sessions closed while recording startup --- .../recording/ComposedRecordingService.java | 146 +++++++++++------- .../server/rest/SessionRestController.java | 2 +- 2 files changed, 91 insertions(+), 57 deletions(-) diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java index e885688d..8eefef94 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java @@ -160,64 +160,97 @@ public class ComposedRecordingService { String containerId = this.sessionsContainers.remove(session.getSessionId()); this.startedRecordings.remove(recording.getId()); - // Gracefully stop ffmpeg process - ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true) - .withAttachStderr(true).withCmd("bash", "-c", "echo 'q' > stop").exec(); - try { - dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback()) - .awaitCompletion(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + if (containerId == null) { + // Session was closed while recording container was initializing + // Wait until containerId is available and force its stop and removal + Thread t = new Thread(() -> { + log.warn("Session closed while starting recording container"); + boolean containerClosed = false; + String containerIdAux; + while (!containerClosed) { + containerIdAux = this.sessionsContainers.remove(session.getSessionId()); + if (containerIdAux == null) { + try { + log.warn("Waiting for container to be launched..."); + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + log.warn("Removing container {} for closed session {}...", containerIdAux, + session.getSessionId()); + dockerClient.stopContainerCmd(containerIdAux).exec(); + this.removeDockerContainer(containerIdAux); + containerClosed = true; + log.warn("Container {} for closed session {} succesfully stopped and removed", containerIdAux, + session.getSessionId()); + log.warn("Deleting unusable files for recording {}", recording.getId()); + if (HttpStatus.NO_CONTENT.equals(this.deleteRecordingFromHost(recording.getId(), true))) { + log.warn("Files properly deleted"); + } + } + } + }); + t.start(); + } else { - // Wait for the container to be gracefully self-stopped - CountDownLatch latch = new CountDownLatch(1); - WaitForContainerStoppedCallback callback = new WaitForContainerStoppedCallback(latch); - dockerClient.waitContainerCmd(containerId).exec(callback); - - boolean stopped = false; - try { - stopped = latch.await(60, TimeUnit.SECONDS); - } catch (InterruptedException e) { - recording.setStatus(Recording.Status.failed); - failRecordingCompletion(containerId, new OpenViduException(Code.RECORDING_COMPLETION_ERROR_CODE, - "The recording completion process has been unexpectedly interrupted")); - } - if (!stopped) { - recording.setStatus(Recording.Status.failed); - failRecordingCompletion(containerId, new OpenViduException(Code.RECORDING_COMPLETION_ERROR_CODE, - "The recording completion process couldn't finish in 60 seconds")); - } - - // Remove container - this.removeDockerContainer(containerId); - - // Update recording attributes reading from video report file - try { - RecordingInfoUtils infoUtils = new RecordingInfoUtils( - this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + ".info"); - - if (openviduConfig.getOpenViduRecordingPublicAccess()) { - recording.setStatus(Recording.Status.available); - } else { - recording.setStatus(Recording.Status.stopped); - } - recording.setDuration(infoUtils.getDurationInSeconds()); - recording.setSize(infoUtils.getSizeInBytes()); - recording.setHasAudio(infoUtils.hasAudio()); - recording.setHasVideo(infoUtils.hasVideo()); - - if (openviduConfig.getOpenViduRecordingPublicAccess()) { - recording.setUrl(this.openviduConfig.getFinalUrl() + "recordings/" + recording.getName() + ".mp4"); + // Gracefully stop ffmpeg process + ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(containerId).withAttachStdout(true) + .withAttachStderr(true).withCmd("bash", "-c", "echo 'q' > stop").exec(); + try { + dockerClient.execStartCmd(execCreateCmdResponse.getId()).exec(new ExecStartResultCallback()) + .awaitCompletion(); + } catch (InterruptedException e) { + e.printStackTrace(); } - } catch (IOException e) { - throw new OpenViduException(Code.RECORDING_REPORT_ERROR_CODE, - "There was an error generating the metadata report file for the recording"); + // Wait for the container to be gracefully self-stopped + CountDownLatch latch = new CountDownLatch(1); + WaitForContainerStoppedCallback callback = new WaitForContainerStoppedCallback(latch); + dockerClient.waitContainerCmd(containerId).exec(callback); + + boolean stopped = false; + try { + stopped = latch.await(60, TimeUnit.SECONDS); + } catch (InterruptedException e) { + recording.setStatus(Recording.Status.failed); + failRecordingCompletion(containerId, new OpenViduException(Code.RECORDING_COMPLETION_ERROR_CODE, + "The recording completion process has been unexpectedly interrupted")); + } + if (!stopped) { + recording.setStatus(Recording.Status.failed); + failRecordingCompletion(containerId, new OpenViduException(Code.RECORDING_COMPLETION_ERROR_CODE, + "The recording completion process couldn't finish in 60 seconds")); + } + + // Remove container + this.removeDockerContainer(containerId); + + // Update recording attributes reading from video report file + try { + RecordingInfoUtils infoUtils = new RecordingInfoUtils( + this.openviduConfig.getOpenViduRecordingPath() + recording.getId() + ".info"); + + if (openviduConfig.getOpenViduRecordingPublicAccess()) { + recording.setStatus(Recording.Status.available); + } else { + recording.setStatus(Recording.Status.stopped); + } + recording.setDuration(infoUtils.getDurationInSeconds()); + recording.setSize(infoUtils.getSizeInBytes()); + recording.setHasAudio(infoUtils.hasAudio()); + recording.setHasVideo(infoUtils.hasVideo()); + + if (openviduConfig.getOpenViduRecordingPublicAccess()) { + recording.setUrl(this.openviduConfig.getFinalUrl() + "recordings/" + recording.getName() + ".mp4"); + } + + } catch (IOException e) { + throw new OpenViduException(Code.RECORDING_REPORT_ERROR_CODE, + "There was an error generating the metadata report file for the recording"); + } + this.sessionHandler.sendRecordingStoppedNotification(session, recording, reason); } - - this.sessionHandler.sendRecordingStoppedNotification(session, recording, reason); - return recording; } @@ -389,9 +422,10 @@ public class ComposedRecordingService { return fileNamesNoExtension; } - public HttpStatus deleteRecordingFromHost(String recordingId) { + public HttpStatus deleteRecordingFromHost(String recordingId, boolean force) { - if (this.startedRecordings.containsKey(recordingId) || this.startingRecordings.containsKey(recordingId)) { + if (!force && (this.startedRecordings.containsKey(recordingId) + || this.startingRecordings.containsKey(recordingId))) { // Cannot delete an active recording return HttpStatus.CONFLICT; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index 59f649ed..0a66a571 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -411,7 +411,7 @@ public class SessionRestController { @RequestMapping(value = "/recordings/{recordingId}", method = RequestMethod.DELETE) public ResponseEntity deleteRecording(@PathVariable("recordingId") String recordingId) { - return new ResponseEntity<>(this.recordingService.deleteRecordingFromHost(recordingId)); + return new ResponseEntity<>(this.recordingService.deleteRecordingFromHost(recordingId, false)); } private ResponseEntity generateErrorResponse(String errorMessage, String path, HttpStatus status) {