Skip to content

Commit 371f272

Browse files
schnapsterfreyacodes
authored andcommitted
Fix race condition in AudioLoader and adjust AudioLoaderRestHandler's usage of it
1 parent d9150bc commit 371f272

File tree

2 files changed

+39
-44
lines changed

2 files changed

+39
-44
lines changed

LavalinkServer/src/main/java/lavalink/server/player/AudioLoader.java

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,70 +31,61 @@
3131
import org.slf4j.LoggerFactory;
3232

3333
import java.util.ArrayList;
34+
import java.util.Collections;
3435
import java.util.List;
36+
import java.util.concurrent.CompletableFuture;
37+
import java.util.concurrent.CompletionStage;
38+
import java.util.concurrent.atomic.AtomicBoolean;
3539

3640
public class AudioLoader implements AudioLoadResultHandler {
3741

3842
private static final Logger log = LoggerFactory.getLogger(AudioLoader.class);
3943
private final AudioPlayerManager audioPlayerManager;
4044

41-
private List<AudioTrack> loadedItems;
42-
private boolean used = false;
45+
private final CompletableFuture<List<AudioTrack>> loadedItems;
46+
private final AtomicBoolean used = new AtomicBoolean(false);
4347

4448
public AudioLoader(AudioPlayerManager audioPlayerManager) {
4549
this.audioPlayerManager = audioPlayerManager;
50+
this.loadedItems = new CompletableFuture<>();
4651
}
4752

48-
List<AudioTrack> loadSync(String identifier) throws InterruptedException {
49-
if(used)
53+
public CompletionStage<List<AudioTrack>> load(String identifier) {
54+
boolean isUsed = this.used.getAndSet(true);
55+
if (isUsed) {
5056
throw new IllegalStateException("This loader can only be used once per instance");
51-
52-
used = true;
53-
54-
audioPlayerManager.loadItem(identifier, this);
55-
56-
synchronized (this) {
57-
this.wait();
5857
}
5958

59+
log.trace("Loading item with identifier {}", identifier);
60+
this.audioPlayerManager.loadItem(identifier, this);
61+
6062
return loadedItems;
6163
}
6264

6365
@Override
6466
public void trackLoaded(AudioTrack audioTrack) {
65-
loadedItems = new ArrayList<>();
66-
loadedItems.add(audioTrack);
6767
log.info("Loaded track " + audioTrack.getInfo().title);
68-
synchronized (this) {
69-
this.notify();
70-
}
68+
ArrayList<AudioTrack> result = new ArrayList<>();
69+
result.add(audioTrack);
70+
this.loadedItems.complete(result);
7171
}
7272

7373
@Override
7474
public void playlistLoaded(AudioPlaylist audioPlaylist) {
7575
log.info("Loaded playlist " + audioPlaylist.getName());
76-
loadedItems = audioPlaylist.getTracks();
77-
synchronized (this) {
78-
this.notify();
79-
}
76+
this.loadedItems.complete(audioPlaylist.getTracks());
8077
}
8178

8279
@Override
8380
public void noMatches() {
8481
log.info("No matches found");
85-
loadedItems = new ArrayList<>();
86-
synchronized (this) {
87-
this.notify();
88-
}
82+
this.loadedItems.complete(Collections.emptyList());
8983
}
9084

9185
@Override
9286
public void loadFailed(FriendlyException e) {
9387
log.error("Load failed", e);
94-
loadedItems = new ArrayList<>();
95-
synchronized (this) {
96-
this.notify();
97-
}
88+
this.loadedItems.complete(Collections.emptyList());
9889
}
9990

10091
}

LavalinkServer/src/main/java/lavalink/server/player/AudioLoaderRestHandler.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import java.io.IOException;
4646
import java.util.List;
4747
import java.util.Optional;
48+
import java.util.concurrent.CompletableFuture;
49+
import java.util.concurrent.CompletionStage;
4850

4951
@RestController
5052
public class AudioLoaderRestHandler {
@@ -91,21 +93,9 @@ private JSONObject trackToJSON(AudioTrack audioTrack) {
9193
.put("position", audioTrack.getPosition());
9294
}
9395

94-
@GetMapping(value = "/loadtracks", produces = "application/json")
95-
@ResponseBody
96-
public ResponseEntity<String> getLoadTracks(HttpServletRequest request, HttpServletResponse response, @RequestParam String identifier)
97-
throws IOException, InterruptedException {
98-
log(request);
99-
100-
Optional<ResponseEntity<String>> notAuthed = checkAuthorization(request);
101-
if (notAuthed.isPresent()) {
102-
return notAuthed.get();
103-
}
104-
96+
private JSONArray encodeTrackList(List<AudioTrack> trackList) {
10597
JSONArray tracks = new JSONArray();
106-
List<AudioTrack> list = new AudioLoader(audioPlayerManager).loadSync(identifier);
107-
108-
list.forEach(track -> {
98+
trackList.forEach(track -> {
10999
JSONObject object = new JSONObject();
110100
object.put("info", trackToJSON(track));
111101

@@ -114,11 +104,25 @@ public ResponseEntity<String> getLoadTracks(HttpServletRequest request, HttpServ
114104
object.put("track", encoded);
115105
tracks.put(object);
116106
} catch (IOException e) {
117-
throw new RuntimeException();
107+
log.warn("Failed to encode a track {}, skipping", track.getIdentifier(), e);
118108
}
119109
});
110+
return tracks;
111+
}
112+
113+
@GetMapping(value = "/loadtracks", produces = "application/json")
114+
@ResponseBody
115+
public CompletionStage<ResponseEntity<String>> getLoadTracks(HttpServletRequest request, @RequestParam String identifier) {
116+
log(request);
117+
118+
Optional<ResponseEntity<String>> notAuthed = checkAuthorization(request);
119+
if (notAuthed.isPresent()) {
120+
return CompletableFuture.completedFuture(notAuthed.get());
121+
}
120122

121-
return new ResponseEntity<>(tracks.toString(), HttpStatus.OK);
123+
return new AudioLoader(audioPlayerManager).load(identifier)
124+
.thenApply(this::encodeTrackList)
125+
.thenApply(tracksArray -> new ResponseEntity<>(tracksArray.toString(), HttpStatus.OK));
122126
}
123127

124128
@GetMapping(value = "/decodetrack", produces = "application/json")

0 commit comments

Comments
 (0)