forked from j62/ctbrec
1
0
Fork 0

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