Skip to content

Instantly share code, notes, and snippets.

@helospark
Created May 8, 2021 11:21
Show Gist options
  • Save helospark/9406f093cc39fe8c4ccf1ea61951e4ee to your computer and use it in GitHub Desktop.
Save helospark/9406f093cc39fe8c4ccf1ea61951e4ee to your computer and use it in GitHub Desktop.
Repro OpenJDK hanging bug on Linux (using ALSA) playback device. It doesn't always work with the same SIZE, delay, and stream size parameters on every machine. I guess it depends on sound hardware, buffers, etc.
package com.test;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
/**
OpenJDK bug report:
---- Title
SourceDataLine.write hangs indefinetely on Linux
---- Description
With some bufferSize and delay combination (see source code) SourceDataLine.write hangs indefinitely.
Some technical notes from debugging this (based on source code attach below):
In method DirectAudioDevice.write, nWrite always returns with 0, therefore loop "while (!flushing)" never returns. (here: https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.desktop/share/classes/com/sun/media/sound/DirectAudioDevice.java#L714)
On nativeCode side this happens because snd_pcm_writei (ALSA function) return -11 error code. (happens here: https://github.com/openjdk/jdk/blob/739769c8fc4b496f08a92225a12d07414537b6c0/src/java.desktop/linux/native/libjsound/PLATFORM_API_LinuxOS_ALSA_PCM.c#L722)
This is from GDB of the same:
722 writtenFrames = snd_pcm_writei(info->handle, (const void*) data, (snd_pcm_uframes_t) frameSize);
(gdb) n
724 if (writtenFrames < 0) {
(gdb) p writtenFrames
$4 = -11
After this, xrun_recovery (ALSA) function is called which returns 0 causing the whole DAUDIO_Write to return 0 therefore 0 is returned to nWrite to Java.
(I'm not entirely sure that "ret <= 0" is the proper condition for xrun_recovery error check, since xrun_recovery returns 0 on success and negative on error. Though this is not likely to be the root cause of this issue)
Some general observations:
- Without the "Thread.sleep(100);" between streaming the first bytes and creating the buffer it usually works. This indicates some race condition to me. On my machine the magic number is 43ms, with 42ms sleep it works, with 43ms it no longer works.
- If the buffer size is as big as the data I'm writing it also works (aka. SIZE instead of SIZE*2 when creating new byte[] array)
- It happens only on Linux
---- System
OS kernel: Linux black-comp 4.15.0-140-generic #144-Ubuntu SMP Fri Mar 19 14:12:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
OS: Ubuntu Mate 18.04 (also reproducible on clean install of latest 20.04)
Java: openjdk version "15" 2020-09-15 (also reproducible on: 17-ea)
ALSA: alsa-base/bionic,bionic,now 1.0.25+dfsg-0ubuntu5 all [installed]
alsa-utils/bionic,now 1.1.3-1ubuntu1 amd64 [installed]
ALSA devices:
aplay --list-devices
**** List of PLAYBACK Hardware Devices ****
card 0: PCH [HDA Intel PCH], device 0: ALC269VC Analog [ALC269VC Analog]
Subdevices: 0/1
Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0]
Subdevices: 1/1
Subdevice #0: subdevice #0
---- Reproduction
Run the main() method from code attached.
---- Expected
Writing data continuously until program ends.
SourceDataLine.write returns after data is written to buffer.
Alternatively if writing data larger than the bufferSize is not supported by the implementation I would expect an Exception to be thrown to indicate the misconfiguration.
In console, the expected result would be something like:
Initializing sourceDataLine with bufferSize=3680
Writing 7360 bytes at 1620469670147
Writing finished at 1620469670153
Writing 7360 bytes at 1620469670153
Writing finished at 1620469670153
Writing 7360 bytes at 1620469670153
Writing finished at 1620469670154
Writing 7360 bytes at 1620469670154
... continues until program is killed
---- Actual
However SourceDataLine.write hangs indefinitely, full log on console:
Initializing sourceDataLine with bufferSize=3680
Writing 7360 bytes at 1620469547701
No further log is printed after this point.
---- Workaround:
- Possibly using larger buffer size.
- Immediately after sourceDataLine.start() write at least 1 empty sample to the SourceDataLine. If done, it will continue to work.
Like:
// ... init
sourceDataLine.start();
byte[] data = new byte[4];
sourceDataLine.write(data, 0, data.length);
*/
public class SourceDataLineWriteHangs {
private static final int CHANNELS = 2;
private static final int BYTES_PER_SAMPLE = 2;
private static final int SAMPLE_RATE = 44100;
private static final int SIZE = 3680;
public static void main(String[] args) throws InterruptedException {
SourceDataLine sourceDataLine = null;
try {
DataLine.Info dataLineInfo;
System.out.println("Initializing sourceDataLine with bufferSize=" + SIZE);
AudioFormat format = new AudioFormat(SAMPLE_RATE, BYTES_PER_SAMPLE * 8, CHANNELS, true, true);
dataLineInfo = new DataLine.Info(SourceDataLine.class, format, SIZE);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(sourceDataLine.getFormat(), SIZE);
sourceDataLine.start();
// byte[] data = new byte[4]; // workaround
// sourceDataLine.write(data, 0, data.length);
} catch (LineUnavailableException | IllegalArgumentException e) {
e.printStackTrace();
}
Thread.sleep(100); // without this it usually works
while (true) {
byte[] data = new byte[SIZE * 2];
System.out.println("Writing " + data.length + " bytes at " + System.currentTimeMillis());
sourceDataLine.write(data, 0, data.length);
System.out.println("Writing finished at " + System.currentTimeMillis());
}
}
}
@iexavl
Copy link

iexavl commented Jun 3, 2024

I am running into this exact same 1 to 1 issue in jdk 21. Have you gotten a handle on how this might be fixed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment