Search code examples
javaandroidmultithreadingscalastack-overflow

Android (in Scala): StackOverflowError depends on when to start a thread?


I have this simple Activity (in Scala, imports ommited):

class TestActivity extends Activity {
  private val TAG = "TestActivity"

  private val mHandler = new Handler {
    override def handleMessage(msg: Message) {
      Log.d(TAG, "handleMessage")
    }
  }

  private val mThread = new Thread {
    override def run {
      mHandler.sendEmptyMessage(0)
      Thread.sleep(10)
      run
    }
  }.start

  override def onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(new TextView(this) {
      setText("hello, world")
    })
  }
}

As you can see, mThread is started immediately, run is overridden tail-recursively, it sends an empty message to mHandler, sleep for a short period and sends the same message again. When the activity starts, I get this error:

....
D/TestActivity(28224): handleMessage
D/TestActivity(28224): handleMessage
D/TestActivity(28224): handleMessage
D/TestActivity(28224): handleMessage
I/dalvikvm(28224): threadid=9: stack overflow on call to Landroid/os/MessageQueue;.nativeWake:VI
I/dalvikvm(28224):   method requires 8+20+0=28 bytes, fp is 0x43e33310 (16 left)
I/dalvikvm(28224):   expanding stack end (0x43e33300 to 0x43e33000)
I/dalvikvm(28224): Shrank stack (to 0x43e33300, curFrame is 0x43e35fe0)
W/dalvikvm(28224): threadid=9: thread exiting with uncaught exception (group=0x40015560)

E/AndroidRuntime(28224): FATAL EXCEPTION: Thread-10
E/AndroidRuntime(28224): java.lang.StackOverflowError
E/AndroidRuntime(28224):  at android.os.MessageQueue.enqueueMessage(MessageQueue.java:223)
E/AndroidRuntime(28224):  at android.os.Handler.sendMessageAtTime(Handler.java:457)
E/AndroidRuntime(28224):  at android.os.Handler.sendMessageDelayed(Handler.java:430)
E/AndroidRuntime(28224):  at android.os.Handler.sendEmptyMessageDelayed(Handler.java:394)
E/AndroidRuntime(28224):  at android.os.Handler.sendEmptyMessage(Handler.java:379)
E/AndroidRuntime(28224):  at com.iped.audiotest.MainActivity$$anon$2.run(Activity.scala:20)
E/AndroidRuntime(28224):  at com.iped.audiotest.MainActivity$$anon$2.run(Activity.scala:22)
E/AndroidRuntime(28224):  at com.iped.audiotest.MainActivity$$anon$2.run(Activity.scala:22)
E/AndroidRuntime(28224):  at com.iped.audiotest.MainActivity$$anon$2.run(Activity.scala:22)
...

Now if I don't start mThread immediately after its creation, like this:

  private val mThread = new Thread {
    override def run {
      mHandler.sendEmptyMessage(0)
      Thread.sleep(10)
      run
    }
  }

and trigger it somewhere else, say, on a touch event:

  override def onTouchEvent(event: MotionEvent): Boolean = {
    if (event.getAction == MotionEvent.ACTION_DOWN)
      mThread.start
    true
  }

things will be just fine.

I can't explain this.


Solution

  • So I did some experiments and I have to conclude that if the Thread with a tail-recursively overridden run is started off in the same expression of its creation, the tail-call optimization will fail (or is any other reason that can cause the error?)

    Bad:

    class Test {
      val mThread = new Thread {
        override def run {
          println("hello")
          run
        }
      }.start
    }
    

    Good:

    class Test {
      val mThread = new Thread {
        override def run {
          println("hello")
          run
        }
      }
      mThread.start
    }
    

    P.S. I'm running Scala 2.9.1, but using 2.8.2 for Android development due to the smaller library size.