watson-developer-cloud / java-sdk

:1st_place_medal: Java SDK to use the IBM Watson services.
http://watson-developer-cloud.github.io/java-sdk/
Apache License 2.0
591 stars 533 forks source link

Unnecessary IOException from SpeechToTextWebSocketListener when socket is closed. #1099

Closed smccants closed 4 years ago

smccants commented 4 years ago

Overview IOException is sometimes thrown when the MicrophoneInputStream is closed.

Expected behavior The exception should not be thrown.

Actual behavior W/System: A resource failed to call end.
W/System: A resource failed to call close. W/System.err: java.io.IOException: Pipe closed W/System.err: at java.io.PipedInputStream.read(PipedInputStream.java:312) W/System.err: at java.io.PipedInputStream.read(PipedInputStream.java:384) W/System.err: at com.ibm.watson.developer_cloud.android.library.audio.MicrophoneInputStream.read(MicrophoneInputStream.java:99)
W/System.err: at com.ibm.watson.developer_cloud.android.library.audio.MicrophoneInputStream.read(MicrophoneInputStream.java:85) W/System.err: at com.ibm.watson.speech_to_text.v1.websocket.SpeechToTextWebSocketListener.sendInputStream(SpeechToTextWebSocketListener.java:195) W/System.err: at com.ibm.watson.speech_to_text.v1.websocket.SpeechToTextWebSocketListener.access$100(SpeechToTextWebSocketListener.java:42) W/System.err: at com.ibm.watson.speech_to_text.v1.websocket.SpeechToTextWebSocketListener$1.run(SpeechToTextWebSocketListener.java:168)

How to reproduce A user action opens successfully a WebSocket with a streaming connection to Watson's Speech to Text. However, I want to automatically close this after ten seconds. This is roughly the code setup:

 SpeechToText speechToText;
 MicrophoneInputStream capture;
  ....
  public synchronized void start() {
       capture = new MicrophoneInputStream(true);
       RecognizeOptions options = new RecognizeOptions.Builder().contentType(ContentType.OPUS.toString()).model("en-US_BroadbandModel").interimResults(true).inactivityTimeout(-1).audio(capture).build();
       speechToText.recognizeUsingWebSocket(options, listener);
  }

When our ten seconds have expired I call:

  capture.close(); 

SDK Version implementation ('com.ibm.watson:ibm-watson:8.0.0') implementation files('libs/speech/ibm-watson-android-sdk-0.5.1.aar')

Additional information: Android 7, 8 and 9

Additional context I suspect the problem is the order of checks in SpeechToTextWebSocketListener.sendInputStream(...) line 195, whicj reads:

 while (((read = inputStream.read(buffer)) > 0) && socketOpen) {

If the socketOpen check was first, then we would not call inputStream.read(...) which throws the IOException, because socketOpen is false in all the cases I've stepped through to reproduce the problem.

smccants commented 4 years ago

Question posted here: https://developer.ibm.com/answers/questions/524417/using-a-timeout-to-close-websocket-for-speech-to-t.html

lpatino10 commented 4 years ago

Hey @smccants, thanks for posting this. I'm going to look through the code now but what you point out about the socketOpen logic makes sense to me at first glance.

I'll report back shortly!

lpatino10 commented 4 years ago

Hi again @smccants. I just got done trying to reproduce the issue and here's what I found. I was modifying the example application to play around with this.

Starting at line 147 in the main activity is where a new thread is spun up to handle speech transcription. Inside this thread I added a sleep and then manually closed the InputStream like you mentioned doing:

new Thread(new Runnable() {
  @Override
  public void run() {
    try {
      speechService.recognizeUsingWebSocket(getRecognizeOptions(capture),
          new MicrophoneRecognizeDelegate());

      // Close InputStream (capture) after 3 seconds.
      try {
        Thread.sleep(3000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      capture.close();
    } catch (Exception e) {
      showError(e);
    }  
  }
}

I was getting the same error, and it turns out this could be fixed by switching around the order of operations in the MicrophoneInputStream class' close() method. The captureThread needs to be closed before the streams so that it doesn't complain about things being closed early. See here for reference.

I'll go ahead and make a PR to submit that change and clear up those exceptions for you. Thanks again for bringing this to my attention!

smccants commented 4 years ago

Hi @lpatino10 . Thanks for looking into this and providing a fix so quickly!