voice icon indicating copy to clipboard operation
voice copied to clipboard

AudioResource memory leak

Open FireNameFN opened this issue 2 years ago • 12 comments

Voice.createAudioResource() creates memory leak. There is no AudioResource.destroy() but without destroy() it eats memory.

With creating resource (eats memory):

Voice.createAudioResource(res.stream, {inputType: res.type, inlineVolume: true});

Without creating resource (all memory is fine):

//Voice.createAudioResource(res.stream, {inputType: res.type, inlineVolume: true});

FireNameFN avatar Aug 07 '21 05:08 FireNameFN

Also if use this resource:

player.play(resource);
player.stop();

Memory will not be freed.

FireNameFN avatar Aug 07 '21 06:08 FireNameFN

If you are using ytdl-core, then it's your coding problem not djs voice issue. Ytdl core explicitly tells everyone to destroy it's stream once you are done downloading.

See this

Don't think that it is issue of voice module.

iim-ayush avatar Aug 07 '21 06:08 iim-ayush

I also destroy ytdl's stream below but AudioResource still takes memory.

res?.stream.destroy();
stream?.destroy();

FireNameFN avatar Aug 07 '21 06:08 FireNameFN

Please send a full code sample

amishshah avatar Aug 07 '21 06:08 amishshah

[TBD]

async #playCycle(guild) {
		let playMusic = async () => {
			try {
				connection.rejoin({channelId: guild.channel});

				var music = this.#channels.getMusic(guild);
				let info = await YTDL.getInfo(music.link, {lang: "ru"});
				music.name = this.#getName(info);

				let bitrate = this.#channels.getChannel(guild).bitrate;

				try {
					var format = YTDL.chooseFormat(info.formats, {quality: "lowestaudio", filter: form => form.bitrate >= bitrate});
					bitrate = format.audioBitrate * 1000;
				} catch {
					var format = YTDL.chooseFormat(info.formats, {quality: "highestaudio", filter: form => form.bitrate < bitrate});
				}

				info.formats = [format];

				var stream = YTDL.downloadFromInfo(info, {highWaterMark: 1 << 30});
				var res = await Voice.demuxProbe(stream);
				let resource = Voice.createAudioResource(res.stream, {inputType: res.type, inlineVolume: true});

				resource.encoder.setBitrate(bitrate);
				resource.volume.volume = guild.volume * this.#settings.volume;
				
				await Voice.entersState(connection, "ready", this.#settings.waitTime);

				guild.player.play(resource);

				await Voice.entersState(guild.player, "playing", this.#settings.waitTime);

				let ann = this.#channels.getAnnouncment(guild);
				if(ann != null)
					sendChannel(ann, this.#settings.messages.info.playing, music.name);

				this.#channels.changeMusic(guild, 1);

				await Voice.entersState(guild.player, "idle", this.#settings.maxTime);
			} catch(e) {
				if(e.message != "Status code: 403") {
					console.log("#playCycle");
					console.log(music.link);
					console.log(guild.player.state);
					console.trace(e);
				}

				let ann = this.#channels.getAnnouncment(guild);
				if(ann != null)
					sendChannel(ann, this.#settings.messages.info.invalid, music.name);

				this.#channels.changeMusic(guild, 1);
			}

			res?.stream.destroy();
			stream?.destroy();
		};

		let canPlay = () => this.#channels.joinable(guild) && guild.musics.length > 0 && guild.channel != null
			&& this.#channels.getChannel(guild).members.size > (this.#channels.getChannel(guild).members.has(this.#channels.getRealGuild(guild).me.id) ? 1 : 0);

		if(canPlay()) {
			this.#channels.stopAwaiting(guild);

			guild.player = Voice.createAudioPlayer();

			var connection = Voice.joinVoiceChannel({
				channelId: guild.channel,
				guildId: guild.id,
				adapterCreator: this.#channels.createAdapter(guild)
			});

			connection.subscribe(guild.player);

			while(canPlay())
				await playMusic();

			connection.destroy();

			guild.player = null;
		}

		this.#channels.await(guild);
	}

Have luck in reading.

FireNameFN avatar Aug 07 '21 06:08 FireNameFN

var stream = YTDL.downloadFromInfo(info, {highWaterMark: 1 << 30});
var res = await Voice.demuxProbe(stream);
let resource = Voice.createAudioResource(res.stream, {inputType: res.type, inlineVolume: true});

🤔 is demuxProbe important here? You have inline volume enabled (inlineVolume: true) which would obviously convert your stream to pcm

twlite avatar Aug 07 '21 07:08 twlite

I also destroy ytdl's stream below but AudioResource still takes memory.

res?.stream.destroy();
stream?.destroy();

I agree with that issue because I have also suffered a memory loss that has destroyed the stream.

cjh980402 avatar Aug 07 '21 10:08 cjh980402

state setter of AudioPlayer does not clean-up operations about resource.volume. Could this be the cause of memory leaks?

if (oldState.status !== AudioPlayerStatus.Idle && oldState.resource !== newResource) {
	oldState.resource.playStream.on('error', noop);
	oldState.resource.playStream.off('error', oldState.onStreamError);
	oldState.resource.audioPlayer = undefined;
	oldState.resource.playStream.destroy();
	oldState.resource.playStream.read(); // required to ensure buffered data is drained, prevents memory leak
}

just operation about playStream

cjh980402 avatar Aug 07 '21 13:08 cjh980402

I don't think this is the issue. The volume stream is included in the pipeline that creates the playStream, so ending the playStream will end the volume stream too.

I still think this is an issue specific to your usage of ytdl-core, as memory leaks don't seem to happen for other users

amishshah avatar Aug 07 '21 13:08 amishshah

Even if I use youtube-dl-exec as the code of the example, but memory usage still increase. Is there a solution to the increase in memory usage?

cjh980402 avatar Aug 07 '21 14:08 cjh980402

Do you wait for the garbage collector?

To detect a memory leak you would need to observe it over 24-72h and check if you see a steady increase in memory, solely from playing music (no new members, no new guilds, etc)

iCrawl avatar Aug 07 '21 15:08 iCrawl

Do you wait for the garbage collector?

To detect a memory leak you would need to observe it over 24-72h and check if you see a steady increase in memory, solely from playing music (no new members, no new guilds, etc)

I tried to wait for GC, but bot very slows down when memory is running out.

FireNameFN avatar Aug 12 '21 04:08 FireNameFN