Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4735740

Java Sound keeps a console app from exiting because of non-daemon event thread

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P4
    • 5.0
    • 1.3.0, 1.4.1, 1.4.2
    • client-libs
    • tiger
    • generic, x86
    • generic, windows_98, windows_2000, windows_xp

    Description



      Name: rmT116609 Date: 08/22/2002


      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 :
      Microsoft 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 object 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)
      ======================================================================

      Attachments

        Issue Links

          Activity

            People

              fbomerssunw Florian Bomers (Inactive)
              rmandalasunw Ranjith Mandala (Inactive)
              Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:
                Imported:
                Indexed: