|
Quick Lists
|
|
Bug ID:
|
4735740
|
|
Votes
|
5
|
|
Synopsis
|
Java Sound keeps a console app from exiting because of non-daemon event thread
|
|
Category
|
java:classes_sound
|
|
Reported Against
|
1.3
, hopper-rc
, mantis-beta
|
|
Release Fixed
|
1.5(tiger)
|
|
State
|
10-Fix Delivered,
bug
|
|
Priority:
|
4-Low
|
|
Related Bugs
|
4365713
,
4380283
,
4435394
,
4785530
,
4872850
,
5070730
|
|
Submit Date
|
22-AUG-2002
|
|
Description
|
FULL PRODUCT VERSION :
java version "1.4.1-rc"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-rc-b19)
Java HotSpot(TM) Client VM (build 1.4.1-rc-b19, mixed mode)
FULL OPERATING SYSTEM VERSION :
customer Windows 2000 [Version 5.00.2195]
A DESCRIPTION OF THE PROBLEM :
When a clip line is obtained, played, and closed, two
threads remain.
1 "Java Sound event dispatcher"
2 Daemon "Headspace mixer frame proc thread"
In a console application, the first thread, since it is not
a Daemon, will keep the application from exiting until that
thread is interrupted. This is not a problem in a Swing
application.
The problem is two-fold:
1 Console application cannot exit properly unless they
find the thread by name and interrupt it. This is not a
solution that is sure to be portable across future Java
Sound API versions.
2 When any type of Line is obtained, these thread
resources are allocated. They stay allocated for the
lifetime of the application. I only know of the threads at
this time but there may be other Java Sound resources that
are hanging around as well. This is poor resource
management. It is fine to allocate them at first need, but
there must also be an API to:
* Detect if Java Sound resources are allocated.
* Release Java Sound resources on demand as long as they
are not needed.
* Pre-allocate Java Sound resources in anticipation of
their need.
The following simple console application demonstrates this
behavior. If not run with the "-i" option, the given sound
file will play but the application will not exit. The thread
dump will show the Java Sound threads, of which the
non-Daemon thread is the culprit. If it is run with the "-i"
option, the application will exit and the thread dump will
show the non-Daemon Java Sound thread no longer exists.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. In a console application, get a
"javax.sound.sampled.Line", which will automatically
allocate Java Sound resources among which is the non-Daemon
"Java Sound event dispatcher" thread.
2. Try to exit the cosole application.
EXPECTED VERSUS ACTUAL BEHAVIOR :
Expected: The console application exits.
Actual : The console application hangs until ctrl-c is pressed.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.io.*;
import javax.sound.sampled.*;
/**
* A simple sound file playing console application using the Java Sound API.
*
* @author Curtis Clauson for The Snake Pit - Development
*/
public
class PlaySound {
/*
* Class Methods
*/
/*
* Plays the given sound file.
*
* @args args The array of command line argument Strings.
*/
public static
void
main(String[] args)
{
// Parse arguments.
final int argCount = args.length;
if (argCount == 0 || argCount > 2) {
System.err.println(
"ERROR: Invalid number of arguments.\n"
+ "usage: java PlaySound [options] <wave file path>\n"
+ "Options:\n"
+ " -i Interrupt the \"Java Sound event dispatcher\" thread on
exit."
);
return;
}
boolean interruptThread = false;
File file = null;
for (int iArg = 0; iArg < argCount; iArg++) {
String arg = args[iArg];
if (arg.equals("-i")) interruptThread = true;
else {
file = new File(arg);
if (!file.exists() || file.isDirectory()) {
System.err.println("ERROR: File not found.");
return;
}
}
}
if (file == null) {
System.err.println("ERROR: No sound file was given.");
return;
}
// Play the sound file.
PlaySound playWav = new PlaySound(file);
playWav.play();
// Deal with and show the active threads.
System.out.println("Done.");
if (interruptThread) {
Thread thread = Utilities.getThread("Java Sound event dispatcher");
if (thread != null) thread.interrupt();
}
Utilities.listThreads();
}
/*
* Instance Data
*/
/** The sound clip of the given file. */
Clip clip;
/*
* Constructors
*/
/*
* Create a PlaySound class.
*/
public
PlaySound(final File file)
{
// Get an AudioInputStream.
AudioInputStream is;
try {
is = AudioSystem.getAudioInputStream(file);
} catch (UnsupportedAudioFileException e) {
System.err.println("Unknown sampled file format.");
return;
} catch (IOException e) {
System.err.println("Error reading the file.");
return;
}
System.out.println("File: " + file.getName() + " Frame Length: " +
is.getFrameLength() + " Format: " + is.getFormat().getEncoding().toString());
// Get a clip.
try {
clip = (Clip)AudioSystem.getLine(new Line.Info(Clip.class));
} catch (LineUnavailableException e) {
System.err.println("No Clip was available.");
return;
}
System.out.println("Clip: " + clip.getLineInfo().toString());
clip.addLineListener(new LineListener() {
public void update(LineEvent event) {
LineEvent.Type type = event.getType();
System.out.println("*** Line Event: " + type.toString() + " ***");
if (type == type.STOP) {
clip.close();
} else if (type == type.CLOSE) {
synchronized(PlaySound.this) {
PlaySound.this.notify();
}
}
}
});
// Open and load the clip from the AudioInputStream.
try {
clip.open(is);
} catch (LineUnavailableException e) {
System.err.println("No Clip was available.");
return;
} catch (IOException e) {
System.err.println("Error reading the file.");
return;
}
}
/*
* Instance Methods
*/
/**
* Play the clip and wait until it finishes.
*/
public synchronized
void
play()
{
if (clip == null) return;
clip.start();
try {wait();} catch (InterruptedException e) {}
}
}
/**
* Thread utility methods.
*/
class Utilities {
/*
* Class Methods
*/
/**
* List all the active thread groups and threads on the console.
*/
public static
Thread
getThread(String name)
{
ThreadGroup root;
for (root = Thread.currentThread().getThreadGroup(); root.getParent() !=
null; root = root.getParent());
Thread[] threads = new Thread[root.activeCount()];
int count = root.enumerate(threads);
for (int iThread = 0; iThread < count; iThread++) {
Thread thread = threads[iThread];
if (thread.getName().equals(name)) return thread;
}
return null;
}
/**
* List all the active thread groups and threads on the console.
*/
public static
void
listThreads()
{
ThreadGroup tgRoot;
for (tgRoot = Thread.currentThread().getThreadGroup();
tgRoot.getParent() != null; tgRoot = tgRoot.getParent());
listThreads(tgRoot, 0);
}
/**
* Recursive method that lists all the thread groups and threads in the
given ThreadGroup.
*/
protected static
void
listThreads(final ThreadGroup root, final int nestLevel)
{
for (int index = 0; index < nestLevel; index++) System.out.print(" ");
System.out.println("ThreadGroup: " + root.getName());
ThreadGroup[] groups = new ThreadGroup[root.activeGroupCount()];
int count = root.enumerate(groups, false);
for (int iGroup = 0; iGroup < count; iGroup++) {
ThreadGroup group = groups[iGroup];
listThreads(group, nestLevel + 1);
}
Thread[] threads = new Thread[root.activeCount()];
count = root.enumerate(threads, false);
for (int iThread = 0; iThread < count; iThread++) {
Thread thread = threads[iThread];
for (int index = 0; index < nestLevel; index++) System.out.print(" ");
System.out.println(
" Thread: "
+ (thread.isDaemon() ? 'D' : ' ')
+ (thread.isAlive() ? 'A' : ' ')
+ (thread.isInterrupted() ? 'I' : ' ')
+ " " + thread.getName()
);
}
}
}
---------- END SOURCE ----------
CUSTOMER WORKAROUND :
In your console application, find the active non-Daemon "Java Sound event dispatcher" Thread customer and call the interrupt() method. The console application will then be able to exit. Note that, since you must depend on the name of the thread, this work around is not sure to be portable
across future versions.
(Review ID: 160615)
======================================================================
|
|
Work Around
|
N/A
|
|
Evaluation
|
xxxxx@xxxxx 2002-09-04
This is a known problem since J2SE v1.3.0 and I'm surprised that there is no bug filed against it yet.
The assumption that this is a memory leak is wrong. The event dispatcher thread is created once for the life time of an application. All per-mixer, and per-line resources are deallocated correctly.
The problem is when to release the event dispatcher thread. I see 2 solutions:
1) make it a daemon thread. Then a program will exit, even if sound is still running, upon termination of all other non-daemon threads
2) keep it as a non-daemon thread, but kill it upon finishing all active playback/recording. Can cause confusion because the VM is exiting with a potential delay. Also it introduces some race conditions and may have quite a resource impact (because the event dispatch thread is created and disposed for every sound that is played). Unfortunately, a thread's daemon state cannot be changed...
Another problem is that most programs already developed for Java Sound deal with this problem...
I think 2) is the better solution, need to discuss this issue with Java Sound users.
xxxxx@xxxxx 2002-12-06
Changed synopsis (old synopsis: "Java Sound resources are never released and can keep a console app from exiting.").
Discussion with various Java Sound users and the javasound-interest mailing list showed that changing the thread into a daemon-thread is not compromising compatibility while solving all the problems. Commit to tiger.
xxxxx@xxxxx 2003-08-06
Interesting reading is bug 4365713, of which I wasn't aware of when fixing this bug. The situation has changed, but not much. Applications that use applet.AudioClip and depend on the fact that the virtual machine will not exit will have to exit at a given point, too, so I don't see a compatibility impact. However, there is a problem that applet.AudioClip doesn't allow to query the duration or to query if the sound is still playing. Like that it is impossible to know when to stop sleeping if you want to ensure that the clip is played to the end. For that scenario, I suggest this workaround: Start playing the audio clip. Then, in a loop, check all mixers in Java Sound that provide Source lines, and check if the mixers are open. If there is no open mixer anymore, it can be assumed that the applet Audioclip has finished playback, and the VM can be exited safely. Still better, of course, is using Java Sound's Clip interface, which gives you a listener and isActive() query capability.
|
|
Comments
|
Submitted On 24-APR-2004
qwecxvb
in javasound-interest archives Jörg Prante provides a
simple work-around call System.exit(0) in your main() function.
see
http://archives.java.sun.com/cgi-bin/wa?A2=ind0002&L=javasound-interest&F=&S=&P=12213
for details
Submitted On 04-AUG-2004
jpyeron
more test cases patch, wanted to verify that an interrupted eventdispatcher could work again.
--- PlaySound.java 2002-08-22 00:00:00.000000000 -0400
+++ PlaySound.java 2004-08-04 09:54:01.816219200 -0400
@@ -13,6 +13,13 @@
* Class Methods
*/
+ public static void main(String args[]) throws Throwable
+ {
+ main2(args);
+ System.out.println("Sleeping 7 seconds");
+ Thread.sleep(7000);
+ main2(args);
+ }
/*
* Plays the given sound file.
@@ -21,8 +28,11 @@
*/
public static
void
- main(String[] args)
+ main2(String[] args)
{
+ System.out.println("Java Vendor :"+System.getProperty("java.vendor"));
+ System.out.println("Java Version:"+System.getProperty("java.version"));
+
// Parse arguments.
final int argCount = args.length;
if (argCount == 0 || argCount > 2) {
@@ -62,6 +72,8 @@
System.out.println("Done.");
if (interruptThread) {
Thread thread = Utilities.getThread("Java Sound event dispatcher");
+ System.out.print("Interrupting: ");
+ Utilities.printThread(thread);
if (thread != null) thread.interrupt();
}
Utilities.listThreads();
@@ -198,6 +210,22 @@
}
/**
+ * Prints the detail for a given thread.
+ */
+ public static
+ void
+ printThread(Thread thread)
+ {
+ System.out.println(
+ " Thread: "
+ + (thread.isDaemon() ? 'D' : ' ')
+ + (thread.isAlive() ? 'A' : ' ')
+ + (thread.isInterrupted() ? 'I' : ' ')
+ + " " + thread.getName()
+ );
+ }
+
+ /**
* Recursive method that lists all the thread groups and threads in the
given ThreadGroup.
*/
@@ -220,13 +248,7 @@
for (int iThread = 0; iThread < count; iThread++) {
Thread thread = threads[iThread];
for (int index = 0; index < nestLevel; index++) System.out.print(" ");
- System.out.println(
- " Thread: "
- + (thread.isDaemon() ? 'D' : ' ')
- + (thread.isAlive() ? 'A' : ' ')
- + (thread.isInterrupted() ? 'I' : ' ')
- + " " + thread.getName()
- );
+ printThread(thread);
}
}
}
Submitted On 04-AUG-2004
jpyeron
Still broken, reopen, using test program
C:\tmp>c:\cygwin\bin\date.exe && java PlaySound TADA.WAV
Wed Aug 4 09:16:35 EDT 2004
Java Vendor :Sun Microsystems Inc.
Java Version:1.4.2_05
File: TADA.WAV Frame Length: 42752 Format: PCM_SIGNED
Clip: interface Clip supporting 8 audio formats, and buffers of 0 to 4194304 bytes
*** Line Event: Open ***
*** Line Event: Start ***
*** Line Event: Stop ***
*** Line Event: Close ***
Done.
ThreadGroup: system
ThreadGroup: main
Thread: A main
Thread: A Java Sound event dispatcher
Thread: DA Headspace mixer frame proc thread
Thread: DA Reference Handler
Thread: DA Finalizer
Thread: DA Signal Dispatcher
Thread: DA CompilerThread0
Submitted On 04-AUG-2004
jpyeron
I have made a patch to test some more:
see notes in bug 4365713
--- PlaySound.java 2002-08-22 00:00:00.000000000 -0400
+++ PlaySound.java 2004-08-04 09:40:21.066049600 -0400
@@ -13,6 +13,13 @@
* Class Methods
*/
+ public static void main(String args[]) throws Throwable
+ {
+ main2(args);
+ System.out.println("Sleeping 7 seconds");
+ Thread.sleep(7000);
+ main2(args);
+ }
/*
* Plays the given sound file.
@@ -21,8 +28,11 @@
*/
public static
void
- main(String[] args)
+ main2(String[] args)
{
+ System.out.println("Java Vendor :"+System.getProperty("java.vendor"));
+ System.out.println("Java Version:"+System.getProperty("java.version"));
+
// Parse arguments.
final int argCount = args.length;
if (argCount == 0 || argCount > 2) {
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|
|
|
 |