Make recorder and RecordingManager thread-safe

This commit is contained in:
0xboobface 2019-06-02 16:51:42 +02:00
parent f2cae6a312
commit da486bbf4a
2 changed files with 131 additions and 112 deletions

View File

@ -101,16 +101,31 @@ public class NextGenLocalRecorder implements Recorder {
try {
Future<Recording> result = completionService.take();
Recording recording = result.get();
recordingsLock.lock();
try {
recordingProcesses.remove(recording.getModel());
} finally {
recordingsLock.unlock();
}
if (recording.getStatus() == State.WAITING) {
LOG.debug("Download finished for {} -> Starting post-processing", recording.getModel().getName());
ppPool.submit(() -> {
try {
setRecordingStatus(recording, State.POST_PROCESSING);
recordingManager.saveRecording(recording);
recording.postprocess();
setRecordingStatus(recording, State.FINISHED);
recordingManager.saveRecording(recording);
return recording;
deleteIfTooShort(recording);
} catch (Exception e) {
LOG.error("Error while post-processing recording {}", recording, e);
recording.setStatus(State.FAILED);
try {
recordingManager.saveRecording(recording);
} catch (IOException e1) {
LOG.error("Couldn't update recording state for recording {}", recording, e1);
}
}
});
// check, if we have to restart the recording
@ -118,19 +133,12 @@ public class NextGenLocalRecorder implements Recorder {
tryRestartRecording(model);
} else {
if(recording.getStatus() != State.DELETED) {
recordingsLock.lock();
try {
recordingManager.delete(recording);
} catch (IOException e) {
LOG.error("Couldn't delete recording {}", recording, e);
} finally {
recordingsLock.unlock();
}
delete(recording);
}
setRecordingStatus(recording, State.FAILED);
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException | ExecutionException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
LOG.error("Error while completing recording", e);
}
}
});
@ -231,7 +239,7 @@ public class NextGenLocalRecorder implements Recorder {
Recording rec = new Recording();
rec.setDownload(download);
rec.setPath(download.getPath(model));
rec.setPath(download.getPath(model).replaceAll("\\\\", "/"));
rec.setModel(model);
rec.setStartDate(Instant.ofEpochMilli(System.currentTimeMillis()));
recordingProcesses.put(model, rec);
@ -241,7 +249,7 @@ public class NextGenLocalRecorder implements Recorder {
setRecordingStatus(rec, State.RECORDING);
recordingManager.saveRecording(rec);
download.start();
boolean deleted = deleteIfTooShort(rec);
boolean deleted = deleteIfEmpty(rec);
setRecordingStatus(rec, deleted ? State.DELETED : State.WAITING);
recordingManager.saveRecording(rec);
} catch (IOException e) {
@ -254,15 +262,19 @@ public class NextGenLocalRecorder implements Recorder {
}
}
private boolean deleteIfTooShort(Recording rec) throws IOException, ParseException, PlaylistException {
// if the size is 0, we don't need to go ahead and check the length
private boolean deleteIfEmpty(Recording rec) throws IOException, ParseException, PlaylistException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
rec.refresh();
long sizeInByte = rec.getSizeInByte();
if (sizeInByte == 0) {
recordingManager.delete(rec);
LOG.info("Deleting empty recording {}", rec);
delete(rec);
return true;
} else {
return false;
}
}
private boolean deleteIfTooShort(Recording rec) throws IOException, ParseException, PlaylistException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
Duration minimumLengthInSeconds = Duration.ofSeconds(Config.getInstance().getSettings().minimumLengthInSeconds);
if (minimumLengthInSeconds.getSeconds() <= 0) {
return false;
@ -270,7 +282,8 @@ public class NextGenLocalRecorder implements Recorder {
Duration recordingLength = rec.getLength();
if (recordingLength.compareTo(minimumLengthInSeconds) < 0) {
recordingManager.delete(rec);
LOG.info("Deleting too short recording {} [{} < {}]", rec, recordingLength, minimumLengthInSeconds);
delete(rec);
return true;
}
@ -293,10 +306,15 @@ public class NextGenLocalRecorder implements Recorder {
modelLock.unlock();
}
recordingsLock.lock();
try {
if (recordingProcesses.containsKey(model)) {
Recording recording = recordingProcesses.get(model);
recording.getDownload().stop();
}
} finally {
recordingsLock.unlock();
}
}
@Override
@ -306,10 +324,15 @@ public class NextGenLocalRecorder implements Recorder {
models.get(index).setStreamUrlIndex(model.getStreamUrlIndex());
config.save();
LOG.debug("Switching stream source to index {} for model {}", model.getStreamUrlIndex(), model.getName());
recordingsLock.lock();
try {
Recording recording = recordingProcesses.get(model);
if (recording != null) {
stopRecordingProcess(model);
}
} finally {
recordingsLock.unlock();
}
tryRestartRecording(model);
} else {
LOG.warn("Couldn't switch stream source for model {}. Not found in list", model.getName());
@ -318,10 +341,15 @@ public class NextGenLocalRecorder implements Recorder {
}
private void stopRecordingProcess(Model model) {
recordingsLock.lock();
try {
LOG.debug("Stopping recording for {}", model);
Recording recording = recordingProcesses.get(model);
LOG.debug("Stopping download for {}", model);
recording.getDownload().stop();
} finally {
recordingsLock.unlock();
}
}
private void stopRecordingProcesses() {
@ -357,12 +385,7 @@ public class NextGenLocalRecorder implements Recorder {
@Override
public List<Recording> getRecordings() throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
recordingsLock.lock();
try {
return recordingManager.getAll();
} finally {
recordingsLock.unlock();
}
}
@Override
@ -377,6 +400,8 @@ public class NextGenLocalRecorder implements Recorder {
recording = false;
LOG.debug("Stopping all recording processes");
recordingsLock.lock();
try {
// make a copy to avoid ConcurrentModificationException
List<Recording> toStop = new ArrayList<>(recordingProcesses.values());
for (Recording rec : toStop) {
@ -392,6 +417,9 @@ public class NextGenLocalRecorder implements Recorder {
LOG.error("Error while waiting for downloads to finish", e);
}
}
} finally {
recordingsLock.unlock();
}
// shutdown threadpools
try {
@ -426,8 +454,13 @@ public class NextGenLocalRecorder implements Recorder {
modelLock.unlock();
}
recordingsLock.lock();
try {
Recording recording = recordingProcesses.get(model);
Optional.ofNullable(recording).map(Recording::getDownload).ifPresent(Download::stop);
} finally {
recordingsLock.unlock();
}
}
@Override
@ -541,6 +574,7 @@ public class NextGenLocalRecorder implements Recorder {
EventBusHolder.BUS.register(new Object() {
@Subscribe
public void modelEvent(Event e) {
recordingsLock.lock();
try {
if (e.getType() == MODEL_ONLINE) {
ModelIsOnlineEvent evt = (ModelIsOnlineEvent) e;
@ -551,6 +585,8 @@ public class NextGenLocalRecorder implements Recorder {
}
} catch (Exception e1) {
LOG.error("Error while handling model state changed event {}", e, e1);
} finally {
recordingsLock.unlock();
}
}
});

View File

@ -9,6 +9,7 @@ import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,7 +32,7 @@ public class RecordingManager {
private Moshi moshi;
private JsonAdapter<Recording> adapter;
private List<Recording> recordings = new ArrayList<>();
// private RecordingFileMonitor monitor = new RecordingFileMonitor(this);
private ReentrantLock recordingsLock = new ReentrantLock();
public RecordingManager(Config config, List<Site> sites) throws IOException {
this.config = config;
@ -42,13 +43,16 @@ public class RecordingManager {
adapter = moshi.adapter(Recording.class).indent(" ");
loadRecordings();
// startMonitoring();
}
public void add(Recording rec) throws UnsupportedEncodingException, IOException {
saveRecording(rec);
recordingsLock.lock();
try {
recordings.add(rec);
// registerFileWatch(rec);
} finally {
recordingsLock.unlock();
}
}
public void saveRecording(Recording rec) throws UnsupportedEncodingException, IOException {
@ -81,35 +85,6 @@ public class RecordingManager {
}
}
// private void startMonitoring() {
// for (Recording recording : recordings) {
// registerFileWatch(recording);
// }
// Thread watcher = new Thread(() -> monitor.processEvents());
// watcher.setDaemon(true);
// watcher.setPriority(Thread.MIN_PRIORITY);
// watcher.setName("RecordingFileMonitor");
// watcher.start();
// }
//
// private void registerFileWatch(Recording recording) {
// File rec = recording.getAbsoluteFile();
// if (rec.isDirectory()) {
// monitor.register(rec.toPath());
// } else {
// monitor.register(rec.getParentFile().toPath());
// }
// }
//
// private void removeFileWatch(Recording recording) {
// File rec = recording.getAbsoluteFile();
// if (rec.isDirectory()) {
// monitor.unregister(rec.toPath());
// } else {
// monitor.unregister(rec.getParentFile().toPath());
// }
// }
private boolean recordingExists(Recording recording) {
File rec = new File(config.getSettings().recordingsDir, recording.getPath());
return rec.exists();
@ -122,6 +97,8 @@ public class RecordingManager {
}
public void delete(Recording recording) throws IOException {
recordingsLock.lock();
try {
int idx = recordings.indexOf(recording);
recording = recordings.get(idx);
@ -145,15 +122,21 @@ public class RecordingManager {
// remove from data structure
recordings.remove(recording);
recording.setStatus(State.DELETED);
// removeFileWatch(recording);
} finally {
recordingsLock.unlock();
}
}
public List<Recording> getAll() {
recordingsLock.lock();
try {
for (Recording recording : recordings) {
recording.refresh();
}
return new ArrayList<>(recordings);
} finally {
recordingsLock.unlock();
}
}
private void deleteEmptyParents(File parent) throws IOException {